source: git/src/img.c @ 452e200

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

Don't leak filename_opened member.

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

  • Property mode set to 100644
File size: 45.4 KB
Line 
1/* img.c
2 * Routines for reading and writing Survex ".3d" image files
3 * Copyright (C) 1993-2004,2005,2006,2010 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      osfree(pimg->filename_opened);
581      fclose(pimg->fh);
582      osfree(pimg);
583      return NULL;
584   }
585
586   pimg->start = ftell(pimg->fh);
587
588   len = strlen(pimg->title);
589   if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
590       pimg->title[len - 11] = '\0';
591       pimg->is_extended_elevation = 1;
592   }
593
594   return pimg;
595}
596
597int
598img_rewind(img *pimg)
599{
600   if (!pimg->fRead) {
601      img_errno = IMG_WRITEERROR;
602      return 0;
603   }
604   if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
605      img_errno = IMG_READERROR;
606      return 0;
607   }
608   clearerr(pimg->fh);
609   /* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
610    * [version 0] not in the middle of a 'LINE' command
611    * [version >= 3] not in the middle of turning a LINE into a MOVE */
612   pimg->pending = 0;
613
614   img_errno = IMG_NONE;
615
616   /* for version >= 3 we use label_buf to store the prefix for reuse */
617   /* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
618    * yet */
619   /* for VERSION_CMAP_SHOT, we store the last station here to detect whether
620    * we MOVE or LINE */
621   pimg->label_len = 0;
622   return 1;
623}
624
625img *
626img_open_write(const char *fnm, char *title_buf, bool fBinary)
627{
628   time_t tm;
629   img *pimg;
630
631   fBinary = fBinary;
632
633   if (fDirectory(fnm)) {
634      img_errno = IMG_DIRECTORY;
635      return NULL;
636   }
637
638   pimg = (img *)xosmalloc(ossizeof(img));
639   if (pimg == NULL) {
640      img_errno = IMG_OUTOFMEMORY;
641      return NULL;
642   }
643
644   pimg->buf_len = 257;
645   pimg->label_buf = xosmalloc(pimg->buf_len);
646   if (!pimg->label_buf) {
647      osfree(pimg);
648      img_errno = IMG_OUTOFMEMORY;
649      return NULL;
650   }
651
652   pimg->fh = fopen(fnm, "wb");
653   if (!pimg->fh) {
654      osfree(pimg->label_buf);
655      osfree(pimg);
656      img_errno = IMG_CANTOPENOUT;
657      return NULL;
658   }
659
660   pimg->filename_opened = NULL;
661
662   /* Output image file header */
663   fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
664   if (img_output_version < 2) {
665      pimg->version = 1;
666      fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
667   } else {
668      pimg->version = (img_output_version > LATEST_IMG_VERSION) ? LATEST_IMG_VERSION : img_output_version;
669      fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
670   }
671   fputsnl(title_buf, pimg->fh);
672   tm = time(NULL);
673   if (tm == (time_t)-1) {
674      fputsnl(TIMENA, pimg->fh);
675   } else {
676      char date[256];
677      /* output current date and time in format specified */
678      strftime(date, 256, TIMEFMT, localtime(&tm));
679      fputsnl(date, pimg->fh);
680   }
681#if 0
682   if (img_output_version >= 5) {
683       static const unsigned char codelengths[32] = {
684           4,  8,  8,  16, 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
685           0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
686       };
687       fwrite(codelengths, 32, 1, pimg->fh);
688   }
689#endif
690   pimg->fRead = fFalse; /* writing to this file */
691   img_errno = IMG_NONE;
692
693   /* for version >= 3 we use label_buf to store the prefix for reuse */
694   pimg->label_buf[0] = '\0';
695   pimg->label_len = 0;
696
697   pimg->date1 = 0;
698   pimg->date2 = 0;
699
700   pimg->olddate1 = 0;
701   pimg->olddate2 = 0;
702
703   pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
704
705   pimg->n_legs = 0;
706   pimg->length = 0.0;
707   pimg->E = pimg->H = pimg->V = 0.0;
708
709   /* Don't check for write errors now - let img_close() report them... */
710   return pimg;
711}
712
713static void
714read_xyz_station_coords(img_point *pt, const char *line)
715{
716   char num[12];
717   memcpy(num, line + 6, 9);
718   num[9] = '\0';
719   pt->x = atof(num) / METRES_PER_FOOT;
720   memcpy(num, line + 15, 9);
721   pt->y = atof(num) / METRES_PER_FOOT;
722   memcpy(num, line + 24, 8);
723   num[8] = '\0';
724   pt->z = atof(num) / METRES_PER_FOOT;
725}
726
727static void
728read_xyz_shot_coords(img_point *pt, const char *line)
729{
730   char num[12];
731   memcpy(num, line + 40, 10);
732   num[10] = '\0';
733   pt->x = atof(num) / METRES_PER_FOOT;
734   memcpy(num, line + 50, 10);
735   pt->y = atof(num) / METRES_PER_FOOT;
736   memcpy(num, line + 60, 9);
737   num[9] = '\0';
738   pt->z = atof(num) / METRES_PER_FOOT;
739}
740
741static void
742subtract_xyz_shot_deltas(img_point *pt, const char *line)
743{
744   char num[12];
745   memcpy(num, line + 15, 9);
746   num[9] = '\0';
747   pt->x -= atof(num) / METRES_PER_FOOT;
748   memcpy(num, line + 24, 8);
749   num[8] = '\0';
750   pt->y -= atof(num) / METRES_PER_FOOT;
751   memcpy(num, line + 32, 8);
752   pt->z -= atof(num) / METRES_PER_FOOT;
753}
754
755static int
756read_coord(FILE *fh, img_point *pt)
757{
758   SVX_ASSERT(fh);
759   SVX_ASSERT(pt);
760   pt->x = get32(fh) / 100.0;
761   pt->y = get32(fh) / 100.0;
762   pt->z = get32(fh) / 100.0;
763   if (ferror(fh) || feof(fh)) {
764      img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
765      return 0;
766   }
767   return 1;
768}
769
770static int
771skip_coord(FILE *fh)
772{
773    return (fseek(fh, 12, SEEK_CUR) == 0);
774}
775
776static int
777read_v3label(img *pimg)
778{
779   char *q;
780   long len = getc(pimg->fh);
781   if (len == EOF) {
782      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
783      return img_BAD;
784   }
785   if (len == 0xfe) {
786      len += get16(pimg->fh);
787      if (feof(pimg->fh)) {
788         img_errno = IMG_BADFORMAT;
789         return img_BAD;
790      }
791      if (ferror(pimg->fh)) {
792         img_errno = IMG_READERROR;
793         return img_BAD;
794      }
795   } else if (len == 0xff) {
796      len = get32(pimg->fh);
797      if (ferror(pimg->fh)) {
798         img_errno = IMG_READERROR;
799         return img_BAD;
800      }
801      if (feof(pimg->fh) || len < 0xfe + 0xffff) {
802         img_errno = IMG_BADFORMAT;
803         return img_BAD;
804      }
805   }
806
807   if (!check_label_space(pimg, pimg->label_len + len + 1)) {
808      img_errno = IMG_OUTOFMEMORY;
809      return img_BAD;
810   }
811   q = pimg->label_buf + pimg->label_len;
812   pimg->label_len += len;
813   if (len && fread(q, len, 1, pimg->fh) != 1) {
814      img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
815      return img_BAD;
816   }
817   q[len] = '\0';
818   return 0;
819}
820
821static int img_read_item_ascii(img *pimg, img_point *p);
822
823int
824img_read_item(img *pimg, img_point *p)
825{
826   int result;
827   pimg->flags = 0;
828
829   if (pimg->version >= 3) {
830      int opt;
831      pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
832      if (pimg->pending == 256) {
833         pimg->pending = 0;
834         return img_XSECT_END;
835      }
836      if (pimg->pending >= 0x80) {
837         *p = pimg->mv;
838         pimg->flags = (int)(pimg->pending) & 0x3f;
839         pimg->pending = 0;
840         return img_LINE;
841      }
842      again3: /* label to goto if we get a prefix, date, or lrud */
843      pimg->label = pimg->label_buf;
844      opt = getc(pimg->fh);
845      if (opt == EOF) {
846         img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
847         return img_BAD;
848      }
849      switch (opt >> 6) {
850       case 0:
851         if (opt == 0) {
852            if (!pimg->label_len) return img_STOP; /* end of data marker */
853            pimg->label_len = 0;
854            goto again3;
855         }
856         if (opt < 15) {
857            /* 1-14 mean trim that many levels from current prefix */
858            int c;
859            if (pimg->label_len <= 17) {
860               /* zero prefix using "0" */
861               img_errno = IMG_BADFORMAT;
862               return img_BAD;
863            }
864            /* extra - 1 because label_len points to one past the end */
865            c = pimg->label_len - 17 - 1;
866            while (pimg->label_buf[c] != '.' || --opt > 0) {
867               if (--c < 0) {
868                  /* zero prefix using "0" */
869                  img_errno = IMG_BADFORMAT;
870                  return img_BAD;
871               }
872            }
873            c++;
874            pimg->label_len = c;
875            goto again3;
876         }
877         if (opt == 15) {
878            result = img_MOVE;
879            break;
880         }
881         if (opt >= 0x20) {
882             switch (opt) {
883                 case 0x20: /* Single date */
884                     pimg->date1 = get32(pimg->fh);
885                     pimg->date2 = pimg->date1;
886                     break;
887                 case 0x21: /* Date range */
888                     pimg->date1 = get32(pimg->fh);
889                     pimg->date2 = get32(pimg->fh);
890                     break;
891                 case 0x22: /* Error info */
892                     pimg->n_legs = get32(pimg->fh);
893                     pimg->length = get32(pimg->fh) / 100.0;
894                     pimg->E = get32(pimg->fh) / 100.0;
895                     pimg->H = get32(pimg->fh) / 100.0;
896                     pimg->V = get32(pimg->fh) / 100.0;
897                     return img_ERROR_INFO;
898                 case 0x30: case 0x31: /* LRUD */
899                 case 0x32: case 0x33: /* Big LRUD! */
900                     if (read_v3label(pimg) == img_BAD) return img_BAD;
901                     pimg->flags = (int)opt & 0x01;
902                     if (opt < 0x32) {
903                         pimg->l = get16(pimg->fh) / 100.0;
904                         pimg->r = get16(pimg->fh) / 100.0;
905                         pimg->u = get16(pimg->fh) / 100.0;
906                         pimg->d = get16(pimg->fh) / 100.0;
907                     } else {
908                         pimg->l = get32(pimg->fh) / 100.0;
909                         pimg->r = get32(pimg->fh) / 100.0;
910                         pimg->u = get32(pimg->fh) / 100.0;
911                         pimg->d = get32(pimg->fh) / 100.0;
912                     }
913                     if (pimg->survey_len) {
914                         size_t l = pimg->survey_len;
915                         const char *s = pimg->label_buf;
916                         if (strncmp(pimg->survey, s, l + 1) != 0) {
917                             return img_XSECT_END;
918                         }
919                         pimg->label += l;
920                         /* skip the dot if there */
921                         if (*pimg->label) pimg->label++;
922                     }
923                     /* If this is the last cross-section in this passage, set
924                      * pending so we return img_XSECT_END next time. */
925                     if (pimg->flags & 0x01) {
926                         pimg->pending = 256;
927                         pimg->flags &= ~0x01;
928                     }
929                     return img_XSECT;
930                 default: /* 0x23 - 0x2f and 0x34 - 0x3f are currently unallocated. */
931                     img_errno = IMG_BADFORMAT;
932                     return img_BAD;
933             }
934             goto again3;
935         }
936         /* 16-31 mean remove (n - 15) characters from the prefix */
937         /* zero prefix using 0 */
938         if (pimg->label_len <= (size_t)(opt - 15)) {
939            img_errno = IMG_BADFORMAT;
940            return img_BAD;
941         }
942         pimg->label_len -= (opt - 15);
943         goto again3;
944       case 1:
945         if (read_v3label(pimg) == img_BAD) return img_BAD;
946
947         result = img_LABEL;
948
949         if (pimg->survey_len) {
950            size_t l = pimg->survey_len;
951            const char *s = pimg->label_buf;
952            if (strncmp(pimg->survey, s, l + 1) != 0) {
953               if (!skip_coord(pimg->fh)) return img_BAD;
954               pimg->pending = 0;
955               goto again3;
956            }
957            pimg->label += l;
958            /* skip the dot if there */
959            if (*pimg->label) pimg->label++;
960         }
961
962         pimg->flags = (int)opt & 0x3f;
963         break;
964       case 2:
965         if (read_v3label(pimg) == img_BAD) return img_BAD;
966
967         result = img_LINE;
968
969         if (pimg->survey_len) {
970            size_t l = pimg->survey_len;
971            const char *s = pimg->label_buf;
972            if (strncmp(pimg->survey, s, l) != 0 ||
973                !(s[l] == '.' || s[l] == '\0')) {
974               if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
975               pimg->pending = 15;
976               goto again3;
977            }
978            pimg->label += l;
979            /* skip the dot if there */
980            if (*pimg->label) pimg->label++;
981         }
982
983         if (pimg->pending) {
984            *p = pimg->mv;
985            if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
986            pimg->pending = opt;
987            return img_MOVE;
988         }
989         pimg->flags = (int)opt & 0x3f;
990         break;
991       default:
992         img_errno = IMG_BADFORMAT;
993         return img_BAD;
994      }
995      if (!read_coord(pimg->fh, p)) return img_BAD;
996      pimg->pending = 0;
997      return result;
998   }
999
1000   pimg->label = pimg->label_buf;
1001
1002   if (pimg->version > 0) {
1003      static long opt_lookahead = 0;
1004      static img_point pt = { 0.0, 0.0, 0.0 };
1005      long opt;
1006      again: /* label to goto if we get a cross */
1007      pimg->label[0] = '\0';
1008
1009      if (pimg->version == 1) {
1010         if (opt_lookahead) {
1011            opt = opt_lookahead;
1012            opt_lookahead = 0;
1013         } else {
1014            opt = get32(pimg->fh);
1015         }
1016      } else {
1017         opt = getc(pimg->fh);
1018      }
1019
1020      if (feof(pimg->fh)) {
1021         img_errno = IMG_BADFORMAT;
1022         return img_BAD;
1023      }
1024      if (ferror(pimg->fh)) {
1025         img_errno = IMG_READERROR;
1026         return img_BAD;
1027      }
1028
1029      switch (opt) {
1030       case -1: case 0:
1031         return img_STOP; /* end of data marker */
1032       case 1:
1033         /* skip coordinates */
1034         if (!skip_coord(pimg->fh)) {
1035            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1036            return img_BAD;
1037         }
1038         goto again;
1039       case 2: case 3: {
1040         char *q;
1041         int ch;
1042         result = img_LABEL;
1043         ch = getc(pimg->fh);
1044         if (ch == EOF) {
1045            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1046            return img_BAD;
1047         }
1048         if (ch != '\\') ungetc(ch, pimg->fh);
1049         fgets(pimg->label_buf, 257, pimg->fh);
1050         if (feof(pimg->fh)) {
1051            img_errno = IMG_BADFORMAT;
1052            return img_BAD;
1053         }
1054         if (ferror(pimg->fh)) {
1055            img_errno = IMG_READERROR;
1056            return img_BAD;
1057         }
1058         q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1059         if (*q != '\n') {
1060            img_errno = IMG_BADFORMAT;
1061            return img_BAD;
1062         }
1063         /* Ignore empty labels in some .3d files (caused by a bug) */
1064         if (q == pimg->label_buf) goto again;
1065         *q = '\0';
1066         pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1067         if (opt == 2) goto done;
1068         break;
1069       }
1070       case 6: case 7: {
1071         long len;
1072         result = img_LABEL;
1073
1074         if (opt == 7)
1075            pimg->flags = getc(pimg->fh);
1076         else
1077            pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
1078
1079         len = get32(pimg->fh);
1080
1081         if (feof(pimg->fh)) {
1082            img_errno = IMG_BADFORMAT;
1083            return img_BAD;
1084         }
1085         if (ferror(pimg->fh)) {
1086            img_errno = IMG_READERROR;
1087            return img_BAD;
1088         }
1089
1090         /* Ignore empty labels in some .3d files (caused by a bug) */
1091         if (len == 0) goto again;
1092         if (!check_label_space(pimg, len + 1)) {
1093            img_errno = IMG_OUTOFMEMORY;
1094            return img_BAD;
1095         }
1096         if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
1097            img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1098            return img_BAD;
1099         }
1100         pimg->label_buf[len] = '\0';
1101         break;
1102       }
1103       case 4:
1104         result = img_MOVE;
1105         break;
1106       case 5:
1107         result = img_LINE;
1108         break;
1109       default:
1110         switch ((int)opt & 0xc0) {
1111          case 0x80:
1112            pimg->flags = (int)opt & 0x3f;
1113            result = img_LINE;
1114            break;
1115          case 0x40: {
1116            char *q;
1117            pimg->flags = (int)opt & 0x3f;
1118            result = img_LABEL;
1119            if (!fgets(pimg->label_buf, 257, pimg->fh)) {
1120               img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1121               return img_BAD;
1122            }
1123            q = pimg->label_buf + strlen(pimg->label_buf) - 1;
1124            /* Ignore empty-labels in some .3d files (caused by a bug) */
1125            if (q == pimg->label_buf) goto again;
1126            if (*q != '\n') {
1127               img_errno = IMG_BADFORMAT;
1128               return img_BAD;
1129            }
1130            *q = '\0';
1131            break;
1132          }
1133          case 0xc0:
1134            /* use this for an extra leg or station flag if we need it */
1135          default:
1136            img_errno = IMG_BADFORMAT;
1137            return img_BAD;
1138         }
1139         break;
1140      }
1141
1142      if (!read_coord(pimg->fh, &pt)) return img_BAD;
1143
1144      if (result == img_LABEL && pimg->survey_len) {
1145         if (strncmp(pimg->label_buf, pimg->survey, pimg->survey_len + 1) != 0)
1146            goto again;
1147         pimg->label += pimg->survey_len + 1;
1148      }
1149
1150      done:
1151      *p = pt;
1152
1153      if (result == img_MOVE && pimg->version == 1) {
1154         /* peek at next code and see if it's an old-style label */
1155         opt_lookahead = get32(pimg->fh);
1156
1157         if (feof(pimg->fh)) {
1158            img_errno = IMG_BADFORMAT;
1159            return img_BAD;
1160         }
1161         if (ferror(pimg->fh)) {
1162            img_errno = IMG_READERROR;
1163            return img_BAD;
1164         }
1165
1166         if (opt_lookahead == 2) return img_read_item(pimg, p);
1167      }
1168
1169      return result;
1170   } else {
1171      /* We need to set the default locale for fscanf() to work on
1172       * numbers with "." as decimal point. */
1173      char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
1174      setlocale(LC_NUMERIC, "C");
1175      result = img_read_item_ascii(pimg, p);
1176      setlocale(LC_NUMERIC, current_locale);
1177      free(current_locale);
1178      return result;
1179   }
1180}
1181
1182/* Handle all for ASCII formats so we can easily fix up the locale
1183 * so fscanf(), etc work on numbers with "." as decimal point. */
1184static int
1185img_read_item_ascii(img *pimg, img_point *p)
1186{
1187   int result;
1188   if (pimg->version == 0) {
1189      ascii_again:
1190      pimg->label[0] = '\0';
1191      if (feof(pimg->fh)) return img_STOP;
1192      if (pimg->pending) {
1193         pimg->pending = 0;
1194         result = img_LINE;
1195      } else {
1196         char cmd[7];
1197         /* Stop if nothing found */
1198         if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
1199         if (strcmp(cmd, "move") == 0)
1200            result = img_MOVE;
1201         else if (strcmp(cmd, "draw") == 0)
1202            result = img_LINE;
1203         else if (strcmp(cmd, "line") == 0) {
1204            /* set flag to indicate to process second triplet as LINE */
1205            pimg->pending = 1;
1206            result = img_MOVE;
1207         } else if (strcmp(cmd, "cross") == 0) {
1208            if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1209               img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
1210               return img_BAD;
1211            }
1212            goto ascii_again;
1213         } else if (strcmp(cmd, "name") == 0) {
1214            size_t off = 0;
1215            int ch = getc(pimg->fh);
1216            if (ch == ' ') ch = getc(pimg->fh);
1217            while (ch != ' ') {
1218               if (ch == '\n' || ch == EOF) {
1219                  img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1220                  return img_BAD;
1221               }
1222               if (off == pimg->buf_len) {
1223                  if (!check_label_space(pimg, pimg->buf_len * 2)) {
1224                     img_errno = IMG_OUTOFMEMORY;
1225                     return img_BAD;
1226                  }
1227               }
1228               pimg->label_buf[off++] = ch;
1229               ch = getc(pimg->fh);
1230            }
1231            pimg->label_buf[off] = '\0';
1232
1233            pimg->label = pimg->label_buf;
1234            if (pimg->label[0] == '\\') pimg->label++;
1235
1236            pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1237
1238            result = img_LABEL;
1239         } else {
1240            img_errno = IMG_BADFORMAT;
1241            return img_BAD; /* unknown keyword */
1242         }
1243      }
1244
1245      if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
1246         img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
1247         return img_BAD;
1248      }
1249
1250      if (result == img_LABEL && pimg->survey_len) {
1251         if (strncmp(pimg->label, pimg->survey, pimg->survey_len + 1) != 0)
1252            goto ascii_again;
1253         pimg->label += pimg->survey_len + 1;
1254      }
1255
1256      return result;
1257   } else if (pimg->version == VERSION_SURVEX_POS) {
1258      /* Survex .pos file */
1259      size_t off;
1260      pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1261      againpos:
1262      off = 0;
1263      while (fscanf(pimg->fh, "(%lf,%lf,%lf ) ", &p->x, &p->y, &p->z) != 3) {
1264         int ch;
1265         if (ferror(pimg->fh)) {
1266            img_errno = IMG_READERROR;
1267            return img_BAD;
1268         }
1269         if (feof(pimg->fh)) return img_STOP;
1270         if (pimg->pending) {
1271            img_errno = IMG_BADFORMAT;
1272            return img_BAD;
1273         }
1274         pimg->pending = 1;
1275         /* ignore rest of line */
1276         do {
1277            ch = getc(pimg->fh);
1278         } while (ch != '\n' && ch != '\r' && ch != EOF);
1279      }
1280
1281      pimg->label_buf[0] = '\0';
1282      while (!feof(pimg->fh)) {
1283         if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
1284            img_errno = IMG_READERROR;
1285            return img_BAD;
1286         }
1287
1288         off += strlen(pimg->label_buf + off);
1289         if (off && pimg->label_buf[off - 1] == '\n') {
1290            pimg->label_buf[off - 1] = '\0';
1291            break;
1292         }
1293         if (!check_label_space(pimg, pimg->buf_len * 2)) {
1294            img_errno = IMG_OUTOFMEMORY;
1295            return img_BAD;
1296         }
1297      }
1298
1299      pimg->label = pimg->label_buf;
1300
1301      if (pimg->label[0] == '\\') pimg->label++;
1302
1303      if (pimg->survey_len) {
1304         size_t l = pimg->survey_len + 1;
1305         if (strncmp(pimg->survey, pimg->label, l) != 0) goto againpos;
1306         pimg->label += l;
1307      }
1308
1309      return img_LABEL;
1310   } else if (pimg->version == VERSION_COMPASS_PLT) {
1311      /* Compass .plt file */
1312      if (pimg->pending > 0) {
1313         /* -1 signals we've entered the first survey we want to
1314          * read, and need to fudge lots if the first action is 'D'...
1315          */
1316         /* pending MOVE or LINE */
1317         int r = pimg->pending - 4;
1318         pimg->pending = 0;
1319         pimg->flags = 0;
1320         pimg->label[pimg->label_len] = '\0';
1321         return r;
1322      }
1323
1324      while (1) {
1325         char *line;
1326         char *q;
1327         size_t len = 0;
1328         int ch = getc(pimg->fh);
1329
1330         switch (ch) {
1331            case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
1332               return img_STOP;
1333            case 'X': case 'F': case 'S':
1334               /* bounding boX (marks end of survey), Feature survey, or
1335                * new Section - skip to next survey */
1336               if (pimg->survey) return img_STOP;
1337skip_to_N:
1338               while (1) {
1339                  do {
1340                     ch = getc(pimg->fh);
1341                  } while (ch != '\n' && ch != '\r' && ch != EOF);
1342                  while (ch == '\n' || ch == '\r') ch = getc(pimg->fh);
1343                  if (ch == 'N') break;
1344                  if (ch == '\x1a' || ch == EOF) return img_STOP;
1345               }
1346               /* FALLTHRU */
1347            case 'N':
1348               line = getline_alloc(pimg->fh);
1349               if (!line) {
1350                  img_errno = IMG_OUTOFMEMORY;
1351                  return img_BAD;
1352               }
1353               while (line[len] > 32) ++len;
1354               if (pimg->label_len == 0) pimg->pending = -1;
1355               if (!check_label_space(pimg, len + 1)) {
1356                  osfree(line);
1357                  img_errno = IMG_OUTOFMEMORY;
1358                  return img_BAD;
1359               }
1360               pimg->label_len = len;
1361               pimg->label = pimg->label_buf;
1362               memcpy(pimg->label, line, len);
1363               pimg->label[len] = '\0';
1364               osfree(line);
1365               break;
1366            case 'M': case 'D': {
1367               /* Move or Draw */
1368               long fpos = -1;
1369               if (pimg->survey && pimg->label_len == 0) {
1370                  /* We're only holding onto this line in case the first line
1371                   * of the 'N' is a 'D', so skip it for now...
1372                   */
1373                  goto skip_to_N;
1374               }
1375               if (ch == 'D' && pimg->pending == -1) {
1376                  if (pimg->survey) {
1377                     fpos = ftell(pimg->fh) - 1;
1378                     fseek(pimg->fh, pimg->start, SEEK_SET);
1379                     ch = getc(pimg->fh);
1380                     pimg->pending = 0;
1381                  } else {
1382                     /* If a file actually has a 'D' before any 'M', then
1383                      * pretend the 'D' is an 'M' - one of the examples
1384                      * in the docs was like this! */
1385                     ch = 'M';
1386                  }
1387               }
1388               line = getline_alloc(pimg->fh);
1389               if (!line) {
1390                  img_errno = IMG_OUTOFMEMORY;
1391                  return img_BAD;
1392               }
1393               /* Compass stores coordinates as North, East, Up = (y,x,z)! */
1394               if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
1395                  osfree(line);
1396                  if (ferror(pimg->fh)) {
1397                     img_errno = IMG_READERROR;
1398                  } else {
1399                     img_errno = IMG_BADFORMAT;
1400                  }
1401                  return img_BAD;
1402               }
1403               p->x *= METRES_PER_FOOT;
1404               p->y *= METRES_PER_FOOT;
1405               p->z *= METRES_PER_FOOT;
1406               q = strchr(line, 'S');
1407               if (!q) {
1408                  osfree(line);
1409                  img_errno = IMG_BADFORMAT;
1410                  return img_BAD;
1411               }
1412               ++q;
1413               len = 0;
1414               while (q[len] > ' ') ++len;
1415               q[len] = '\0';
1416               len += 2; /* ' ' and '\0' */
1417               if (!check_label_space(pimg, pimg->label_len + len)) {
1418                  img_errno = IMG_OUTOFMEMORY;
1419                  return img_BAD;
1420               }
1421               pimg->label = pimg->label_buf;
1422               if (pimg->label_len) {
1423                   pimg->label[pimg->label_len] = ' ';
1424                   memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
1425               } else {
1426                   memcpy(pimg->label, q, len - 1);
1427               }
1428               q += len - 1;
1429               /* Now read LRUD.  Technically, this is optional but virtually
1430                * all PLT files have it (with dummy negative values if no LRUD
1431                * was measured) and some versions of Compass can't read PLT
1432                * files without it!
1433                */
1434               while (*q && *q <= ' ') q++;
1435               if (*q == 'P') {
1436                   if (sscanf(q + 1, "%lf%lf%lf%lf",
1437                              &pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
1438                       osfree(line);
1439                       if (ferror(pimg->fh)) {
1440                           img_errno = IMG_READERROR;
1441                       } else {
1442                           img_errno = IMG_BADFORMAT;
1443                       }
1444                       return img_BAD;
1445                   }
1446                   pimg->l *= METRES_PER_FOOT;
1447                   pimg->r *= METRES_PER_FOOT;
1448                   pimg->u *= METRES_PER_FOOT;
1449                   pimg->d *= METRES_PER_FOOT;
1450               } else {
1451                   pimg->l = pimg->r = pimg->u = pimg->d = -1;
1452               }
1453               osfree(line);
1454               pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
1455               if (fpos != -1) {
1456                  fseek(pimg->fh, fpos, SEEK_SET);
1457               } else {
1458                  pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
1459               }
1460               return img_LABEL;
1461            }
1462            default:
1463               img_errno = IMG_BADFORMAT;
1464               return img_BAD;
1465         }
1466      }
1467   } else {
1468      /* CMAP .xyz file */
1469      char *line = NULL;
1470      char *q;
1471      size_t len;
1472
1473      if (pimg->pending) {
1474         /* pending MOVE or LINE or LABEL or STOP */
1475         int r = pimg->pending - 4;
1476         /* Set label to empty - don't use "" as we adjust label relative
1477          * to label_buf when label_buf is reallocated. */
1478         pimg->label = pimg->label_buf + strlen(pimg->label_buf);
1479         pimg->flags = 0;
1480         if (r == img_LABEL) {
1481            /* nasty magic */
1482            read_xyz_shot_coords(p, pimg->label_buf + 16);
1483            subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
1484            pimg->pending = img_STOP + 4;
1485            return img_MOVE;
1486         }
1487
1488         pimg->pending = 0;
1489
1490         if (r == img_STOP) {
1491            /* nasty magic */
1492            read_xyz_shot_coords(p, pimg->label_buf + 16);
1493            return img_LINE;
1494         }
1495
1496         return r;
1497      }
1498
1499      pimg->label = pimg->label_buf;
1500      do {
1501         osfree(line);
1502         if (feof(pimg->fh)) return img_STOP;
1503         line = getline_alloc(pimg->fh);
1504         if (!line) {
1505            img_errno = IMG_OUTOFMEMORY;
1506            return img_BAD;
1507         }
1508      } while (line[0] == ' ' || line[0] == '\0');
1509      if (line[0] == '\x1a') return img_STOP;
1510
1511      len = strlen(line);
1512      if (pimg->version == VERSION_CMAP_STATION) {
1513         /* station variant */
1514         if (len < 37) {
1515            osfree(line);
1516            img_errno = IMG_BADFORMAT;
1517            return img_BAD;
1518         }
1519         memcpy(pimg->label, line, 6);
1520         q = memchr(pimg->label, ' ', 6);
1521         if (!q) q = pimg->label + 6;
1522         *q = '\0';
1523
1524         read_xyz_station_coords(p, line);
1525
1526         /* FIXME: look at prev for lines (line + 32, 5) */
1527         /* FIXME: duplicate stations... */
1528         return img_LABEL;
1529      } else {
1530         /* Shot variant */
1531         char old[8], new[8];
1532         if (len < 61) {
1533            osfree(line);
1534            img_errno = IMG_BADFORMAT;
1535            return img_BAD;
1536         }
1537
1538         memcpy(old, line, 7);
1539         q = memchr(old, ' ', 7);
1540         if (!q) q = old + 7;
1541         *q = '\0';
1542
1543         memcpy(new, line + 7, 7);
1544         q = memchr(new, ' ', 7);
1545         if (!q) q = new + 7;
1546         *q = '\0';
1547
1548         pimg->flags = img_SFLAG_UNDERGROUND;
1549
1550         if (strcmp(old, new) == 0) {
1551            pimg->pending = img_MOVE + 4;
1552            read_xyz_shot_coords(p, line);
1553            strcpy(pimg->label, new);
1554            osfree(line);
1555            return img_LABEL;
1556         }
1557
1558         if (strcmp(old, pimg->label) == 0) {
1559            pimg->pending = img_LINE + 4;
1560            read_xyz_shot_coords(p, line);
1561            strcpy(pimg->label, new);
1562            osfree(line);
1563            return img_LABEL;
1564         }
1565
1566         pimg->pending = img_LABEL + 4;
1567         read_xyz_shot_coords(p, line);
1568         strcpy(pimg->label, new);
1569         memcpy(pimg->label + 16, line, 70);
1570
1571         osfree(line);
1572         return img_LABEL;
1573      }
1574   }
1575}
1576
1577static int
1578write_v3label(img *pimg, int opt, const char *s)
1579{
1580   size_t len, n, dot;
1581
1582   /* find length of common prefix */
1583   dot = 0;
1584   for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
1585      if (s[len] == '.') dot = len + 1;
1586   }
1587
1588   SVX_ASSERT(len <= pimg->label_len);
1589   n = pimg->label_len - len;
1590   if (len == 0) {
1591      if (pimg->label_len) putc(0, pimg->fh);
1592   } else if (n <= 16) {
1593      if (n) putc(n + 15, pimg->fh);
1594   } else if (dot == 0) {
1595      if (pimg->label_len) putc(0, pimg->fh);
1596      len = 0;
1597   } else {
1598      const char *p = pimg->label_buf + dot;
1599      n = 1;
1600      for (len = pimg->label_len - dot - 17; len; len--) {
1601         if (*p++ == '.') n++;
1602      }
1603      if (n <= 14) {
1604         putc(n, pimg->fh);
1605         len = dot;
1606      } else {
1607         if (pimg->label_len) putc(0, pimg->fh);
1608         len = 0;
1609      }
1610   }
1611
1612   n = strlen(s + len);
1613   putc(opt, pimg->fh);
1614   if (n < 0xfe) {
1615      putc(n, pimg->fh);
1616   } else if (n < 0xffff + 0xfe) {
1617      putc(0xfe, pimg->fh);
1618      put16((short)(n - 0xfe), pimg->fh);
1619   } else {
1620      putc(0xff, pimg->fh);
1621      put32(n, pimg->fh);
1622   }
1623   fwrite(s + len, n, 1, pimg->fh);
1624
1625   n += len;
1626   pimg->label_len = n;
1627   if (!check_label_space(pimg, n + 1))
1628      return 0; /* FIXME: distinguish out of memory... */
1629   memcpy(pimg->label_buf + len, s + len, n - len + 1);
1630
1631   return !ferror(pimg->fh);
1632}
1633
1634static void
1635img_write_item_date(img *pimg)
1636{
1637    if (pimg->date1 != pimg->olddate1 ||
1638            pimg->date2 != pimg->olddate2) {
1639        /* Only write dates when they've changed. */
1640        if (pimg->date1 == pimg->date2) {
1641            putc(0x20, pimg->fh);
1642            put32(pimg->date1, pimg->fh);
1643        } else {
1644            putc(0x21, pimg->fh);
1645            put32(pimg->date1, pimg->fh);
1646            put32(pimg->date2, pimg->fh);
1647        }
1648        pimg->olddate1 = pimg->date1;
1649        pimg->olddate2 = pimg->date2;
1650    }
1651}
1652
1653void
1654img_write_item(img *pimg, int code, int flags, const char *s,
1655               double x, double y, double z)
1656{
1657   if (!pimg) return;
1658   if (pimg->version >= 3) {
1659      int opt = 0;
1660      switch (code) {
1661       case img_LABEL:
1662         write_v3label(pimg, 0x40 | flags, s);
1663         opt = 0;
1664         break;
1665       case img_XSECT: {
1666         INT32_T l, r, u, d, max_dim;
1667         /* Need at least version 5 for img_XSECT. */
1668         if (pimg->version < 5) break;
1669         img_write_item_date(pimg);
1670         l = (INT32_T)my_round(pimg->l * 100.0);
1671         r = (INT32_T)my_round(pimg->r * 100.0);
1672         u = (INT32_T)my_round(pimg->u * 100.0);
1673         d = (INT32_T)my_round(pimg->d * 100.0);
1674         if (l < 0) l = -1;
1675         if (r < 0) r = -1;
1676         if (u < 0) u = -1;
1677         if (d < 0) d = -1;
1678         max_dim = max(max(l, r), max(u, d));
1679         flags &= 1;
1680         if (max_dim >= 32768) flags |= 2;
1681         write_v3label(pimg, 0x30 | flags, s);
1682         if (flags & 2) {
1683            /* Big passage!  Need to use 4 bytes. */
1684            put32(l, pimg->fh);
1685            put32(r, pimg->fh);
1686            put32(u, pimg->fh);
1687            put32(d, pimg->fh);
1688         } else {
1689            put16(l, pimg->fh);
1690            put16(r, pimg->fh);
1691            put16(u, pimg->fh);
1692            put16(d, pimg->fh);
1693         }
1694         return;
1695       }
1696       case img_MOVE:
1697         opt = 15;
1698         break;
1699       case img_LINE:
1700         if (pimg->version >= 4) {
1701             img_write_item_date(pimg);
1702         }
1703         write_v3label(pimg, 0x80 | flags, s ? s : "");
1704         opt = 0;
1705         break;
1706       default: /* ignore for now */
1707         return;
1708      }
1709      if (opt) putc(opt, pimg->fh);
1710      /* Output in cm */
1711      put32((INT32_T)my_round(x * 100.0), pimg->fh);
1712      put32((INT32_T)my_round(y * 100.0), pimg->fh);
1713      put32((INT32_T)my_round(z * 100.0), pimg->fh);
1714   } else {
1715      size_t len;
1716      INT32_T opt = 0;
1717      SVX_ASSERT(pimg->version > 0);
1718      switch (code) {
1719       case img_LABEL:
1720         if (pimg->version == 1) {
1721            /* put a move before each label */
1722            img_write_item(pimg, img_MOVE, 0, NULL, x, y, z);
1723            put32(2, pimg->fh);
1724            fputsnl(s, pimg->fh);
1725            return;
1726         }
1727         len = strlen(s);
1728         if (len > 255 || strchr(s, '\n')) {
1729            /* long label - not in early incarnations of v2 format, but few
1730             * 3d files will need these, so better not to force incompatibility
1731             * with a new version I think... */
1732            putc(7, pimg->fh);
1733            putc(flags, pimg->fh);
1734            put32(len, pimg->fh);
1735            fputs(s, pimg->fh);
1736         } else {
1737            putc(0x40 | (flags & 0x3f), pimg->fh);
1738            fputsnl(s, pimg->fh);
1739         }
1740         opt = 0;
1741         break;
1742       case img_MOVE:
1743         opt = 4;
1744         break;
1745       case img_LINE:
1746         if (pimg->version > 1) {
1747            opt = 0x80 | (flags & 0x3f);
1748            break;
1749         }
1750         opt = 5;
1751         break;
1752       default: /* ignore for now */
1753         return;
1754      }
1755      if (pimg->version == 1) {
1756         put32(opt, pimg->fh);
1757      } else {
1758         if (opt) putc(opt, pimg->fh);
1759      }
1760      /* Output in cm */
1761      put32((INT32_T)my_round(x * 100.0), pimg->fh);
1762      put32((INT32_T)my_round(y * 100.0), pimg->fh);
1763      put32((INT32_T)my_round(z * 100.0), pimg->fh);
1764   }
1765}
1766
1767/* Write error information for the current traverse
1768 * n_legs is the number of legs in the traverse
1769 * length is the traverse length (in m)
1770 * E is the ratio of the observed misclosure to the theoretical one
1771 * H is the ratio of the observed horizontal misclosure to the theoretical one
1772 * V is the ratio of the observed vertical misclosure to the theoretical one
1773 */
1774void
1775img_write_errors(img *pimg, int n_legs, double length,
1776                 double E, double H, double V)
1777{
1778    putc(0x22, pimg->fh);
1779    put32(n_legs, pimg->fh);
1780    put32((INT32_T)my_round(length * 100.0), pimg->fh);
1781    put32((INT32_T)my_round(E * 100.0), pimg->fh);
1782    put32((INT32_T)my_round(H * 100.0), pimg->fh);
1783    put32((INT32_T)my_round(V * 100.0), pimg->fh);
1784}
1785
1786int
1787img_close(img *pimg)
1788{
1789   int result = 1;
1790   if (pimg) {
1791      if (pimg->fh) {
1792         if (pimg->fRead) {
1793            osfree(pimg->survey);
1794            osfree(pimg->title);
1795            osfree(pimg->datestamp);
1796         } else {
1797            /* write end of data marker */
1798            switch (pimg->version) {
1799             case 1:
1800               put32((INT32_T)-1, pimg->fh);
1801               break;
1802             case 2:
1803               putc(0, pimg->fh);
1804               break;
1805             default:
1806               if (pimg->label_len) putc(0, pimg->fh);
1807               putc(0, pimg->fh);
1808               break;
1809            }
1810         }
1811         if (ferror(pimg->fh)) result = 0;
1812         if (fclose(pimg->fh)) result = 0;
1813         if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
1814      }
1815      osfree(pimg->label_buf);
1816      osfree(pimg->filename_opened);
1817      osfree(pimg);
1818   }
1819   return result;
1820}
Note: See TracBrowser for help on using the repository browser.