kavea/src/img.c

2779 lines
70 KiB
C

/* img.c
* Routines for reading and writing Survex ".3d" image files
* Copyright (C) 1993-2021 Olly Betts
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "img.h"
#define TIMENA "?"
#ifdef IMG_HOSTED
# define INT32_T int32_t
# define UINT32_T uint32_t
# include "debug.h"
# include "filelist.h"
# include "filename.h"
# include "message.h"
# include "useful.h"
# define TIMEFMT msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107)
#else
# ifdef HAVE_STDINT_H
# include <stdint.h>
# define INT32_T int32_t
# define UINT32_T uint32_t
# else
# include <limits.h>
# if INT_MAX >= 2147483647
# define INT32_T int
# define UINT32_T unsigned
# else
# define INT32_T long
# define UINT32_T unsigned long
# endif
# endif
# define TIMEFMT "%a,%Y.%m.%d %H:%M:%S %Z"
# define EXT_SVX_3D "3d"
# define EXT_SVX_POS "pos"
# define FNM_SEP_EXT '.'
# define METRES_PER_FOOT 0.3048 /* exact value */
# define xosmalloc(L) malloc((L))
# define xosrealloc(L,S) realloc((L),(S))
# define osfree(P) free((P))
# define osnew(T) (T*)malloc(sizeof(T))
/* in IMG_HOSTED mode, this tests if a filename refers to a directory */
# define fDirectory(X) 0
/* open file FNM with mode MODE, maybe using path PTH and/or extension EXT */
/* path isn't used in img.c, but EXT is */
# define fopenWithPthAndExt(PTH,FNM,EXT,MODE,X) \
((*(X) = NULL), fopen(FNM,MODE))
# ifndef PUTC
# define PUTC(C, FH) putc(C, FH)
# endif
# ifndef GETC
# define GETC(FH) getc(FH)
# endif
# define fputsnl(S, FH) (fputs((S), (FH)) == EOF ? EOF : putc('\n', (FH)))
# define SVX_ASSERT(X)
#ifdef __cplusplus
# include <algorithm>
using std::max;
using std::min;
#else
/* Return max/min of two numbers. */
/* May be defined already (e.g. by Borland C in stdlib.h) */
/* NB Bad news if X or Y has side-effects... */
# ifndef max
# define max(X, Y) ((X) > (Y) ? (X) : (Y))
# endif
# ifndef min
# define min(X, Y) ((X) < (Y) ? (X) : (Y))
# endif
#endif
static INT32_T
get32(FILE *fh)
{
UINT32_T w = GETC(fh);
w |= (UINT32_T)GETC(fh) << 8l;
w |= (UINT32_T)GETC(fh) << 16l;
w |= (UINT32_T)GETC(fh) << 24l;
return (INT32_T)w;
}
static void
put32(UINT32_T w, FILE *fh)
{
PUTC((char)(w), fh);
PUTC((char)(w >> 8l), fh);
PUTC((char)(w >> 16l), fh);
PUTC((char)(w >> 24l), fh);
}
static short
get16(FILE *fh)
{
UINT32_T w = GETC(fh);
w |= (UINT32_T)GETC(fh) << 8l;
return (short)w;
}
static void
put16(short word, FILE *fh)
{
unsigned short w = (unsigned short)word;
PUTC((char)(w), fh);
PUTC((char)(w >> 8l), fh);
}
static char *
baseleaf_from_fnm(const char *fnm)
{
const char *p;
const char *q;
char * res;
size_t len;
p = fnm;
q = strrchr(p, '/');
if (q) p = q + 1;
q = strrchr(p, '\\');
if (q) p = q + 1;
q = strrchr(p, FNM_SEP_EXT);
if (q) len = (const char *)q - p; else len = strlen(p);
res = (char *)xosmalloc(len + 1);
if (!res) return NULL;
memcpy(res, p, len);
res[len] = '\0';
return res;
}
#endif
static char * my_strdup(const char *str);
static time_t
mktime_with_tz(struct tm * tm, const char * tz)
{
time_t r;
char * old_tz = getenv("TZ");
#ifdef _MSC_VER
if (old_tz) {
old_tz = my_strdup(old_tz);
if (!old_tz)
return (time_t)-1;
}
if (_putenv_s("TZ", tz) != 0) {
osfree(old_tz);
return (time_t)-1;
}
#elif defined HAVE_SETENV
if (old_tz) {
old_tz = my_strdup(old_tz);
if (!old_tz)
return (time_t)-1;
}
if (setenv("TZ", tz, 1) < 0) {
osfree(old_tz);
return (time_t)-1;
}
#else
char * p;
if (old_tz) {
size_t len = strlen(old_tz) + 1;
p = (char *)xosmalloc(len + 3);
if (!p)
return (time_t)-1;
memcpy(p, "TZ=", 3);
memcpy(p + 3, tz, len);
old_tz = p;
}
p = (char *)xosmalloc(strlen(tz) + 4);
if (!p) {
osfree(old_tz);
return (time_t)-1;
}
memcpy(p, "TZ=", 3);
strcpy(p + 3, tz);
if (putenv(p) != 0) {
osfree(p);
osfree(old_tz);
return (time_t)-1;
}
#define CLEANUP() osfree(p)
#endif
tzset();
r = mktime(tm);
if (old_tz) {
#ifdef _MSC_VER
_putenv_s("TZ", old_tz);
#elif !defined HAVE_SETENV
putenv(old_tz);
#else
setenv("TZ", old_tz, 1);
#endif
osfree(old_tz);
} else {
#ifdef _MSC_VER
_putenv_s("TZ", "");
#elif !defined HAVE_UNSETENV
putenv((char*)"TZ");
#else
unsetenv("TZ");
#endif
}
#ifdef CLEANUP
CLEANUP();
#undef CLEANUP
#endif
return r;
}
static unsigned short
getu16(FILE *fh)
{
return (unsigned short)get16(fh);
}
#include <math.h>
#if !defined HAVE_LROUND && !defined HAVE_DECL_LROUND
/* The autoconf tests are not in use, but C99 and C++11 both added lround(),
* so set HAVE_LROUND and HAVE_DECL_LROUND conservatively based on the language
* standard version the compiler claims to support. */
# if (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) || \
(defined __cplusplus && __cplusplus >= 201103L)
# define HAVE_LROUND 1
# define HAVE_DECL_LROUND 1
# endif
#endif
#ifdef HAVE_LROUND
# if defined HAVE_DECL_LROUND && !HAVE_DECL_LROUND
/* On older systems, the prototype may be missing. */
extern long lround(double);
# endif
# define my_lround lround
#else
static long
my_lround(double x) {
return (x >= 0.0) ? (long)(x + 0.5) : -(long)(0.5 - x);
}
#endif
/* portable case insensitive string compare */
#if defined(strcasecmp) || defined(HAVE_STRCASECMP)
# define my_strcasecmp strcasecmp
#else
static int my_strcasecmp(const char *s1, const char *s2) {
unsigned char c1, c2;
do {
c1 = *s1++;
c2 = *s2++;
} while (c1 && toupper(c1) == toupper(c2));
/* now calculate real difference */
return c1 - c2;
}
#endif
unsigned int img_output_version = IMG_VERSION_MAX;
static img_errcode img_errno = IMG_NONE;
#define FILEID "Survex 3D Image File"
#define EXT_PLT "plt"
#define EXT_PLF "plf"
/* Attempt to string paste to ensure we are passed a literal string */
#define LITLEN(S) (sizeof(S"") - 1)
/* Fake "version numbers" for non-3d formats we can read. */
#define VERSION_CMAP_SHOT -4
#define VERSION_CMAP_STATION -3
#define VERSION_COMPASS_PLT -2
#define VERSION_SURVEX_POS -1
static char *
my_strdup(const char *str)
{
char *p;
size_t len = strlen(str) + 1;
p = (char *)xosmalloc(len);
if (p) memcpy(p, str, len);
return p;
}
#define getline_alloc(FH) getline_alloc_len(FH, NULL)
static char *
getline_alloc_len(FILE *fh, size_t * p_len)
{
int ch;
size_t i = 0;
size_t len = 16;
char *buf = (char *)xosmalloc(len);
if (!buf) return NULL;
ch = GETC(fh);
while (ch != '\n' && ch != '\r' && ch != EOF) {
buf[i++] = ch;
if (i == len - 1) {
char *p;
len += len;
p = (char *)xosrealloc(buf, len);
if (!p) {
osfree(buf);
return NULL;
}
buf = p;
}
ch = GETC(fh);
}
if (ch == '\n' || ch == '\r') {
int otherone = ch ^ ('\n' ^ '\r');
ch = GETC(fh);
/* if it's not the other eol character, put it back */
if (ch != otherone) ungetc(ch, fh);
}
buf[i] = '\0';
if (p_len) *p_len = i;
return buf;
}
img_errcode
img_error(void)
{
return img_errno;
}
static int
check_label_space(img *pimg, size_t len)
{
if (len > pimg->buf_len) {
char *b = (char *)xosrealloc(pimg->label_buf, len);
if (!b) return 0;
pimg->label = (pimg->label - pimg->label_buf) + b;
pimg->label_buf = b;
pimg->buf_len = len;
}
return 1;
}
/* Check if a station name should be included. */
static int
stn_included(img *pimg)
{
if (!pimg->survey_len) return 1;
size_t l = pimg->survey_len;
const char *s = pimg->label_buf;
if (strncmp(pimg->survey, s, l + 1) != 0) {
return 0;
}
pimg->label += l + 1;
return 1;
}
/* Check if a survey name should be included. */
static int
survey_included(img *pimg)
{
if (!pimg->survey_len) return 1;
size_t l = pimg->survey_len;
const char *s = pimg->label_buf;
if (strncmp(pimg->survey, s, l) != 0 ||
!(s[l] == '.' || s[l] == '\0')) {
return 0;
}
pimg->label += l;
/* skip the dot if there */
if (*pimg->label) pimg->label++;
return 1;
}
/* Check if a survey name in a buffer should be included.
*
* For "foreign" formats which just have one level of surveys.
*/
static int
buf_included(img *pimg, const char *buf, size_t len)
{
return pimg->survey_len == len && strncmp(buf, pimg->survey, len) == 0;
}
#define has_ext(F,L,E) ((L) > LITLEN(E) + 1 &&\
(F)[(L) - LITLEN(E) - 1] == FNM_SEP_EXT &&\
my_strcasecmp((F) + (L) - LITLEN(E), E) == 0)
img *
img_open_survey(const char *fnm, const char *survey)
{
img *pimg;
FILE *fh;
char* filename_opened = NULL;
if (fDirectory(fnm)) {
img_errno = IMG_DIRECTORY;
return NULL;
}
fh = fopenWithPthAndExt("", fnm, EXT_SVX_3D, "rb", &filename_opened);
pimg = img_read_stream_survey(fh, fclose,
filename_opened ? filename_opened : fnm,
survey);
if (pimg) {
pimg->filename_opened = filename_opened;
} else {
osfree(filename_opened);
}
return pimg;
}
img *
img_read_stream_survey(FILE *stream, int (*close_func)(FILE*),
const char *fnm,
const char *survey)
{
img *pimg;
size_t len;
char buf[LITLEN(FILEID) + 9];
int ch;
if (stream == NULL) {
img_errno = IMG_FILENOTFOUND;
return NULL;
}
pimg = osnew(img);
if (pimg == NULL) {
img_errno = IMG_OUTOFMEMORY;
if (close_func) close_func(stream);
return NULL;
}
pimg->fh = stream;
pimg->close_func = close_func;
pimg->buf_len = 257;
pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
if (!pimg->label_buf) {
if (pimg->close_func) pimg->close_func(pimg->fh);
osfree(pimg);
img_errno = IMG_OUTOFMEMORY;
return NULL;
}
pimg->fRead = 1; /* reading from this file */
img_errno = IMG_NONE;
pimg->flags = 0;
pimg->filename_opened = NULL;
/* for version >= 3 we use label_buf to store the prefix for reuse */
/* for VERSION_COMPASS_PLT, 0 value indicates we haven't
* entered a survey yet */
/* for VERSION_CMAP_SHOT, we store the last station here
* to detect whether we MOVE or LINE */
pimg->label_len = 0;
pimg->label_buf[0] = '\0';
pimg->survey = NULL;
pimg->survey_len = 0;
pimg->separator = '.';
#if IMG_API_VERSION == 0
pimg->date1 = pimg->date2 = 0;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = pimg->days2 = -1;
#endif
pimg->is_extended_elevation = 0;
pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
pimg->title = pimg->datestamp = pimg->cs = NULL;
pimg->datestamp_numeric = (time_t)-1;
if (survey) {
len = strlen(survey);
if (len) {
if (survey[len - 1] == '.') len--;
if (len) {
char *p;
pimg->survey = (char *)xosmalloc(len + 2);
if (!pimg->survey) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
memcpy(pimg->survey, survey, len);
/* Set title to leaf survey name */
pimg->survey[len] = '\0';
p = strrchr(pimg->survey, '.');
if (p) p++; else p = pimg->survey;
pimg->title = my_strdup(p);
if (!pimg->title) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
pimg->survey[len] = '.';
pimg->survey[len + 1] = '\0';
}
}
pimg->survey_len = len;
}
/* [VERSION_COMPASS_PLT, VERSION_CMAP_STATION, VERSION_CMAP_SHOT] pending
* IMG_LINE or IMG_MOVE - both have 4 added.
* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
* [version 0] not in the middle of a 'LINE' command
* [version >= 3] not in the middle of turning a LINE into a MOVE
*/
pimg->pending = 0;
len = strlen(fnm);
if (has_ext(fnm, len, EXT_SVX_POS)) {
pos_file:
pimg->version = VERSION_SURVEX_POS;
if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
pimg->datestamp = my_strdup(TIMENA);
if (!pimg->datestamp) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
pimg->start = 0;
return pimg;
}
if (has_ext(fnm, len, EXT_PLT) || has_ext(fnm, len, EXT_PLF)) {
long fpos;
plt_file:
pimg->version = VERSION_COMPASS_PLT;
/* Spaces aren't legal in Compass station names, but dots are, so
* use space as the level separator */
pimg->separator = ' ';
pimg->start = 0;
if (!pimg->survey) pimg->title = baseleaf_from_fnm(fnm);
pimg->datestamp = my_strdup(TIMENA);
if (!pimg->datestamp) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
while (1) {
ch = GETC(pimg->fh);
switch (ch) {
case '\x1a':
fseek(pimg->fh, -1, SEEK_CUR);
/* FALL THRU */
case EOF:
pimg->start = ftell(pimg->fh);
return pimg;
case 'N': {
char *line, *q;
fpos = ftell(pimg->fh) - 1;
if (!pimg->survey) {
/* FIXME : if there's only one survey in the file, it'd be nice
* to use its description as the title here...
*/
ungetc('N', pimg->fh);
pimg->start = fpos;
return pimg;
}
line = getline_alloc(pimg->fh);
if (!line) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
len = 0;
while (line[len] > 32) ++len;
if (!buf_included(pimg, line, len)) {
osfree(line);
continue;
}
q = strchr(line + len, 'C');
if (q && q[1]) {
osfree(pimg->title);
pimg->title = my_strdup(q + 1);
} else if (!pimg->title) {
pimg->title = my_strdup(pimg->label);
}
osfree(line);
if (!pimg->title) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
if (!pimg->start) pimg->start = fpos;
fseek(pimg->fh, pimg->start, SEEK_SET);
return pimg;
}
case 'M': case 'D':
pimg->start = ftell(pimg->fh) - 1;
break;
}
while (ch != '\n' && ch != '\r') {
ch = GETC(pimg->fh);
}
}
}
/* Although these are often referred to as "CMAP .XYZ files", it seems
* that actually, the extension .XYZ isn't used, rather .SHT (shot
* variant, produced by CMAP v16 and later), .UNA (unadjusted) and
* .ADJ (adjusted) extensions are. Since img has long checked for
* .XYZ, we continue to do so in case anyone is relying on it.
*/
if (has_ext(fnm, len, "sht") ||
has_ext(fnm, len, "adj") ||
has_ext(fnm, len, "una") ||
has_ext(fnm, len, "xyz")) {
char *line;
xyz_file:
/* Spaces aren't legal in CMAP station names, but dots are, so
* use space as the level separator. */
pimg->separator = ' ';
line = getline_alloc(pimg->fh);
if (!line) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
/* There doesn't seem to be a spec for what happens after 1999 with cmap
* files, so this code allows for:
* * 21xx -> xx (up to 2150)
* * 21xx -> 1xx (up to 2199)
* * full year being specified instead of 2 digits
*/
len = strlen(line);
if (len > 59) {
/* Don't just truncate at column 59, allow for a > 2 digit year. */
char * p = strstr(line + len, "Page");
if (p) {
while (p > line && p[-1] == ' ')
--p;
*p = '\0';
len = p - line;
} else {
line[59] = '\0';
}
}
if (len > 45) {
/* YY/MM/DD HH:MM */
struct tm tm;
unsigned long v;
char * p;
pimg->datestamp = my_strdup(line + 45);
p = pimg->datestamp;
v = strtoul(p, &p, 10);
if (v <= 50) {
/* In the absence of a spec for cmap files, assume <= 50 means 21st
* century. */
v += 2000;
} else if (v < 200) {
/* Map 100-199 to 21st century. */
v += 1900;
}
if (v == ULONG_MAX || *p++ != '/')
goto bad_cmap_date;
tm.tm_year = v - 1900;
v = strtoul(p, &p, 10);
if (v < 1 || v > 12 || *p++ != '/')
goto bad_cmap_date;
tm.tm_mon = v - 1;
v = strtoul(p, &p, 10);
if (v < 1 || v > 31 || *p++ != ' ')
goto bad_cmap_date;
tm.tm_mday = v;
v = strtoul(p, &p, 10);
if (v >= 24 || *p++ != ':')
goto bad_cmap_date;
tm.tm_hour = v;
v = strtoul(p, &p, 10);
if (v >= 60)
goto bad_cmap_date;
tm.tm_min = v;
if (*p == ':') {
v = strtoul(p + 1, &p, 10);
if (v > 60)
goto bad_cmap_date;
tm.tm_sec = v;
} else {
tm.tm_sec = 0;
}
tm.tm_isdst = 0;
/* We have no indication of what timezone this timestamp is in. It's
* probably local time for whoever processed the data, so just assume
* UTC, which is at least fairly central in the possibilities.
*/
pimg->datestamp_numeric = mktime_with_tz(&tm, "");
} else {
pimg->datestamp = my_strdup(TIMENA);
}
bad_cmap_date:
if (strncmp(line, " Cave Survey Data Processed by CMAP ",
LITLEN(" Cave Survey Data Processed by CMAP ")) == 0) {
len = 0;
} else {
if (len > 45) {
line[45] = '\0';
len = 45;
}
while (len > 2 && line[len - 1] == ' ') --len;
if (len > 2) {
line[len] = '\0';
pimg->title = my_strdup(line + 2);
}
}
if (len <= 2) pimg->title = baseleaf_from_fnm(fnm);
osfree(line);
if (!pimg->datestamp || !pimg->title) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
line = getline_alloc(pimg->fh);
if (!line) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
if (line[0] != ' ' || (line[1] != 'S' && line[1] != 'O')) {
img_errno = IMG_BADFORMAT;
goto error;
}
if (line[1] == 'S') {
pimg->version = VERSION_CMAP_STATION;
} else {
pimg->version = VERSION_CMAP_SHOT;
}
osfree(line);
line = getline_alloc(pimg->fh);
if (!line) {
img_errno = IMG_OUTOFMEMORY;
goto error;
}
if (line[0] != ' ' || line[1] != '-') {
img_errno = IMG_BADFORMAT;
goto error;
}
osfree(line);
pimg->start = ftell(pimg->fh);
return pimg;
}
if (fread(buf, LITLEN(FILEID) + 1, 1, pimg->fh) != 1 ||
memcmp(buf, FILEID"\n", LITLEN(FILEID) + 1) != 0) {
if (fread(buf + LITLEN(FILEID) + 1, 8, 1, pimg->fh) == 1 &&
memcmp(buf, FILEID"\r\nv0.01\r\n", LITLEN(FILEID) + 9) == 0) {
/* v0 3d file with DOS EOLs */
pimg->version = 0;
goto v03d;
}
rewind(pimg->fh);
if (buf[1] == ' ') {
if (buf[0] == ' ') {
/* Looks like a CMAP .xyz file ... */
goto xyz_file;
} else if (strchr("ZSNF", buf[0])) {
/* Looks like a Compass .plt file ... */
/* Almost certainly it'll start "Z " */
goto plt_file;
}
}
if (buf[0] == '(') {
/* Looks like a Survex .pos file ... */
goto pos_file;
}
img_errno = IMG_BADFORMAT;
goto error;
}
/* check file format version */
ch = GETC(pimg->fh);
pimg->version = 0;
if (tolower(ch) == 'b') {
/* binary file iff B/b prefix */
pimg->version = 1;
ch = GETC(pimg->fh);
}
if (ch != 'v') {
img_errno = IMG_BADFORMAT;
goto error;
}
ch = GETC(pimg->fh);
if (ch == '0') {
if (fread(buf, 4, 1, pimg->fh) != 1 || memcmp(buf, ".01\n", 4) != 0) {
img_errno = IMG_BADFORMAT;
goto error;
}
/* nothing special to do */
} else if (pimg->version == 0) {
if (ch < '2' || ch > '0' + IMG_VERSION_MAX || GETC(pimg->fh) != '\n') {
img_errno = IMG_TOONEW;
goto error;
}
pimg->version = ch - '0';
} else {
img_errno = IMG_BADFORMAT;
goto error;
}
v03d:
{
size_t title_len;
char * title = getline_alloc_len(pimg->fh, &title_len);
if (pimg->version == 8 && title) {
/* We sneak in an extra field after a zero byte here, containing the
* specified coordinate system (if any). Older readers will just
* not see it (which is fine), and this trick avoids us having to
* bump the 3d format version.
*/
size_t real_len = strlen(title);
if (real_len != title_len) {
pimg->cs = my_strdup(title + real_len + 1);
}
}
if (!pimg->title) {
pimg->title = title;
} else {
osfree(title);
}
}
pimg->datestamp = getline_alloc(pimg->fh);
if (!pimg->title || !pimg->datestamp) {
img_errno = IMG_OUTOFMEMORY;
error:
osfree(pimg->title);
osfree(pimg->cs);
osfree(pimg->datestamp);
osfree(pimg->filename_opened);
if (pimg->close_func) pimg->close_func(pimg->fh);
osfree(pimg);
return NULL;
}
if (pimg->version >= 8) {
int flags = GETC(pimg->fh);
if (flags & img_FFLAG_EXTENDED) pimg->is_extended_elevation = 1;
} else {
len = strlen(pimg->title);
if (len > 11 && strcmp(pimg->title + len - 11, " (extended)") == 0) {
pimg->title[len - 11] = '\0';
pimg->is_extended_elevation = 1;
}
}
if (pimg->datestamp[0] == '@') {
unsigned long v;
char * p;
errno = 0;
v = strtoul(pimg->datestamp + 1, &p, 10);
if (errno == 0 && *p == '\0')
pimg->datestamp_numeric = v;
/* FIXME: We're assuming here that the C time_t epoch is 1970, which is
* true for Unix-like systems, macOS and Windows, but isn't guaranteed
* by ISO C.
*/
} else {
/* %a,%Y.%m.%d %H:%M:%S %Z */
struct tm tm;
unsigned long v;
char * p = pimg->datestamp;
while (isalpha((unsigned char)*p)) ++p;
if (*p == ',') ++p;
while (isspace((unsigned char)*p)) ++p;
v = strtoul(p, &p, 10);
if (v == ULONG_MAX || *p++ != '.')
goto bad_3d_date;
tm.tm_year = v - 1900;
v = strtoul(p, &p, 10);
if (v < 1 || v > 12 || *p++ != '.')
goto bad_3d_date;
tm.tm_mon = v - 1;
v = strtoul(p, &p, 10);
if (v < 1 || v > 31 || *p++ != ' ')
goto bad_3d_date;
tm.tm_mday = v;
v = strtoul(p, &p, 10);
if (v >= 24 || *p++ != ':')
goto bad_3d_date;
tm.tm_hour = v;
v = strtoul(p, &p, 10);
if (v >= 60 || *p++ != ':')
goto bad_3d_date;
tm.tm_min = v;
v = strtoul(p, &p, 10);
if (v > 60)
goto bad_3d_date;
tm.tm_sec = v;
tm.tm_isdst = 0;
while (isspace((unsigned char)*p)) ++p;
/* p now points to the timezone string.
*
* However, it's likely to be a string like "BST", and such strings can
* be ambiguous (BST could be UTC+1 or UTC+6), so it is impossible to
* reliably convert in all cases. Just pass what we have to tzset() - if
* it doesn't handle it, UTC will be used.
*/
pimg->datestamp_numeric = mktime_with_tz(&tm, p);
}
bad_3d_date:
pimg->start = ftell(pimg->fh);
return pimg;
}
int
img_rewind(img *pimg)
{
if (!pimg->fRead) {
img_errno = IMG_WRITEERROR;
return 0;
}
if (fseek(pimg->fh, pimg->start, SEEK_SET) != 0) {
img_errno = IMG_READERROR;
return 0;
}
clearerr(pimg->fh);
/* [VERSION_SURVEX_POS] already skipped heading line, or there wasn't one
* [version 0] not in the middle of a 'LINE' command
* [version >= 3] not in the middle of turning a LINE into a MOVE */
pimg->pending = 0;
img_errno = IMG_NONE;
/* for version >= 3 we use label_buf to store the prefix for reuse */
/* for VERSION_COMPASS_PLT, 0 value indicates we haven't entered a survey
* yet */
/* for VERSION_CMAP_SHOT, we store the last station here to detect whether
* we MOVE or LINE */
pimg->label_len = 0;
pimg->style = img_STYLE_UNKNOWN;
return 1;
}
img *
img_open_write_cs(const char *fnm, const char *title, const char *cs, int flags)
{
if (fDirectory(fnm)) {
img_errno = IMG_DIRECTORY;
return NULL;
}
return img_write_stream(fopen(fnm, "wb"), fclose, title, cs, flags);
}
img *
img_write_stream(FILE *stream, int (*close_func)(FILE*),
const char *title, const char *cs, int flags)
{
time_t tm;
img *pimg;
if (stream == NULL) {
img_errno = IMG_FILENOTFOUND;
return NULL;
}
pimg = osnew(img);
if (pimg == NULL) {
img_errno = IMG_OUTOFMEMORY;
if (close_func) close_func(stream);
return NULL;
}
pimg->fh = stream;
pimg->close_func = close_func;
pimg->buf_len = 257;
pimg->label_buf = (char *)xosmalloc(pimg->buf_len);
if (!pimg->label_buf) {
if (pimg->close_func) pimg->close_func(pimg->fh);
osfree(pimg);
img_errno = IMG_OUTOFMEMORY;
return NULL;
}
pimg->filename_opened = NULL;
/* Output image file header */
fputs("Survex 3D Image File\n", pimg->fh); /* file identifier string */
if (img_output_version < 2) {
pimg->version = 1;
fputs("Bv0.01\n", pimg->fh); /* binary file format version number */
} else {
pimg->version = (img_output_version > IMG_VERSION_MAX) ? IMG_VERSION_MAX : img_output_version;
fprintf(pimg->fh, "v%d\n", pimg->version); /* file format version no. */
}
fputs(title, pimg->fh);
if (pimg->version < 8 && (flags & img_FFLAG_EXTENDED)) {
/* Older format versions append " (extended)" to the title to mark
* extended elevations. */
size_t len = strlen(title);
if (len < 11 || strcmp(title + len - 11, " (extended)") != 0)
fputs(" (extended)", pimg->fh);
}
if (pimg->version == 8 && cs && *cs) {
/* We sneak in an extra field after a zero byte here, containing the
* specified coordinate system (if any). Older readers will just not
* see it (which is fine), and this trick avoids us having to bump the
* 3d format version.
*/
PUTC('\0', pimg->fh);
fputs(cs, pimg->fh);
}
PUTC('\n', pimg->fh);
tm = time(NULL);
if (tm == (time_t)-1) {
fputsnl(TIMENA, pimg->fh);
} else if (pimg->version <= 7) {
char date[256];
/* output current date and time in format specified */
strftime(date, 256, TIMEFMT, localtime(&tm));
fputsnl(date, pimg->fh);
} else {
fprintf(pimg->fh, "@%ld\n", (long)tm);
}
if (pimg->version >= 8) {
/* Clear bit one in case anyone has been passing true for fBinary. */
flags &=~ 1;
PUTC(flags, pimg->fh);
}
#if 0
if (img_output_version >= 5) {
static const unsigned char codelengths[32] = {
4, 8, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
fwrite(codelengths, 32, 1, pimg->fh);
}
#endif
pimg->fRead = 0; /* writing to this file */
img_errno = IMG_NONE;
/* for version >= 3 we use label_buf to store the prefix for reuse */
pimg->label_buf[0] = '\0';
pimg->label_len = 0;
#if IMG_API_VERSION == 0
pimg->date1 = pimg->date2 = 0;
pimg->olddate1 = pimg->olddate2 = 0;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = pimg->days2 = -1;
pimg->olddays1 = pimg->olddays2 = -1;
#endif
pimg->style = pimg->oldstyle = img_STYLE_UNKNOWN;
pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
pimg->n_legs = 0;
pimg->length = 0.0;
pimg->E = pimg->H = pimg->V = 0.0;
/* Don't check for write errors now - let img_close() report them... */
return pimg;
}
static void
read_xyz_station_coords(img_point *pt, const char *line)
{
char num[12];
memcpy(num, line + 6, 9);
num[9] = '\0';
pt->x = atof(num) / METRES_PER_FOOT;
memcpy(num, line + 15, 9);
pt->y = atof(num) / METRES_PER_FOOT;
memcpy(num, line + 24, 8);
num[8] = '\0';
pt->z = atof(num) / METRES_PER_FOOT;
}
static void
read_xyz_shot_coords(img_point *pt, const char *line)
{
char num[12];
memcpy(num, line + 40, 10);
num[10] = '\0';
pt->x = atof(num) / METRES_PER_FOOT;
memcpy(num, line + 50, 10);
pt->y = atof(num) / METRES_PER_FOOT;
memcpy(num, line + 60, 9);
num[9] = '\0';
pt->z = atof(num) / METRES_PER_FOOT;
}
static void
subtract_xyz_shot_deltas(img_point *pt, const char *line)
{
char num[12];
memcpy(num, line + 15, 9);
num[9] = '\0';
pt->x -= atof(num) / METRES_PER_FOOT;
memcpy(num, line + 24, 8);
num[8] = '\0';
pt->y -= atof(num) / METRES_PER_FOOT;
memcpy(num, line + 32, 8);
pt->z -= atof(num) / METRES_PER_FOOT;
}
static int
read_coord(FILE *fh, img_point *pt)
{
SVX_ASSERT(fh);
SVX_ASSERT(pt);
pt->x = get32(fh) / 100.0;
pt->y = get32(fh) / 100.0;
pt->z = get32(fh) / 100.0;
if (ferror(fh) || feof(fh)) {
img_errno = feof(fh) ? IMG_BADFORMAT : IMG_READERROR;
return 0;
}
return 1;
}
static int
skip_coord(FILE *fh)
{
return (fseek(fh, 12, SEEK_CUR) == 0);
}
static int
read_v3label(img *pimg)
{
char *q;
long len = GETC(pimg->fh);
if (len == EOF) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
if (len == 0xfe) {
len += get16(pimg->fh);
if (feof(pimg->fh)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
} else if (len == 0xff) {
len = get32(pimg->fh);
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
if (feof(pimg->fh) || len < 0xfe + 0xffff) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
}
if (!check_label_space(pimg, pimg->label_len + len + 1)) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
q = pimg->label_buf + pimg->label_len;
pimg->label_len += len;
if (len && fread(q, len, 1, pimg->fh) != 1) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
q[len] = '\0';
return 0;
}
static int
read_v8label(img *pimg, int common_flag, size_t common_val)
{
char *q;
size_t del, add;
if (common_flag) {
if (common_val == 0) return 0;
add = del = common_val;
} else {
int ch = GETC(pimg->fh);
if (ch == EOF) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
if (ch != 0x00) {
del = ch >> 4;
add = ch & 0x0f;
} else {
ch = GETC(pimg->fh);
if (ch == EOF) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
if (ch != 0xff) {
del = ch;
} else {
del = get32(pimg->fh);
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
}
ch = GETC(pimg->fh);
if (ch == EOF) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
if (ch != 0xff) {
add = ch;
} else {
add = get32(pimg->fh);
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
}
}
if (add > del && !check_label_space(pimg, pimg->label_len + add - del + 1)) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
}
if (del > pimg->label_len) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
pimg->label_len -= del;
q = pimg->label_buf + pimg->label_len;
pimg->label_len += add;
if (add && fread(q, add, 1, pimg->fh) != 1) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
q[add] = '\0';
return 0;
}
static int img_read_item_new(img *pimg, img_point *p);
static int img_read_item_v3to7(img *pimg, img_point *p);
static int img_read_item_ancient(img *pimg, img_point *p);
static int img_read_item_ascii_wrapper(img *pimg, img_point *p);
static int img_read_item_ascii(img *pimg, img_point *p);
int
img_read_item(img *pimg, img_point *p)
{
pimg->flags = 0;
if (pimg->version >= 8) {
return img_read_item_new(pimg, p);
} else if (pimg->version >= 3) {
return img_read_item_v3to7(pimg, p);
} else if (pimg->version >= 1) {
return img_read_item_ancient(pimg, p);
} else {
return img_read_item_ascii_wrapper(pimg, p);
}
}
static int
img_read_item_new(img *pimg, img_point *p)
{
int result;
int opt;
pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
if (pimg->pending >= 0x40) {
if (pimg->pending == 256) {
pimg->pending = 0;
return img_XSECT_END;
}
*p = pimg->mv;
pimg->flags = (int)(pimg->pending) & 0x3f;
pimg->pending = 0;
return img_LINE;
}
again3: /* label to goto if we get a prefix, date, or lrud */
pimg->label = pimg->label_buf;
opt = GETC(pimg->fh);
if (opt == EOF) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
if (opt >> 6 == 0) {
if (opt <= 4) {
if (opt == 0 && pimg->style == 0)
return img_STOP; /* end of data marker */
/* STYLE */
pimg->style = opt;
goto again3;
}
if (opt >= 0x10) {
switch (opt) {
case 0x10: { /* No date info */
#if IMG_API_VERSION == 0
pimg->date1 = pimg->date2 = 0;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = pimg->days2 = -1;
#endif
break;
}
case 0x11: { /* Single date */
int days1 = (int)getu16(pimg->fh);
#if IMG_API_VERSION == 0
pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
#else /* IMG_API_VERSION == 1 */
pimg->days2 = pimg->days1 = days1;
#endif
break;
}
case 0x12: { /* Date range (short) */
int days1 = (int)getu16(pimg->fh);
int days2 = days1 + GETC(pimg->fh) + 1;
#if IMG_API_VERSION == 0
pimg->date1 = (days1 - 25567) * 86400;
pimg->date2 = (days2 - 25567) * 86400;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = days1;
pimg->days2 = days2;
#endif
break;
}
case 0x13: { /* Date range (long) */
int days1 = (int)getu16(pimg->fh);
int days2 = (int)getu16(pimg->fh);
#if IMG_API_VERSION == 0
pimg->date1 = (days1 - 25567) * 86400;
pimg->date2 = (days2 - 25567) * 86400;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = days1;
pimg->days2 = days2;
#endif
break;
}
case 0x1f: /* Error info */
pimg->n_legs = get32(pimg->fh);
pimg->length = get32(pimg->fh) / 100.0;
pimg->E = get32(pimg->fh) / 100.0;
pimg->H = get32(pimg->fh) / 100.0;
pimg->V = get32(pimg->fh) / 100.0;
return img_ERROR_INFO;
case 0x30: case 0x31: /* LRUD */
case 0x32: case 0x33: /* Big LRUD! */
if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
pimg->flags = (int)opt & 0x01;
if (opt < 0x32) {
pimg->l = get16(pimg->fh) / 100.0;
pimg->r = get16(pimg->fh) / 100.0;
pimg->u = get16(pimg->fh) / 100.0;
pimg->d = get16(pimg->fh) / 100.0;
} else {
pimg->l = get32(pimg->fh) / 100.0;
pimg->r = get32(pimg->fh) / 100.0;
pimg->u = get32(pimg->fh) / 100.0;
pimg->d = get32(pimg->fh) / 100.0;
}
if (!stn_included(pimg)) {
return img_XSECT_END;
}
/* If this is the last cross-section in this passage, set
* pending so we return img_XSECT_END next time. */
if (pimg->flags & 0x01) {
pimg->pending = 256;
pimg->flags &= ~0x01;
}
return img_XSECT;
default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
img_errno = IMG_BADFORMAT;
return img_BAD;
}
goto again3;
}
if (opt != 15) {
/* 1-14 and 16-31 reserved */
img_errno = IMG_BADFORMAT;
return img_BAD;
}
result = img_MOVE;
} else if (opt >= 0x80) {
if (read_v8label(pimg, 0, 0) == img_BAD) return img_BAD;
result = img_LABEL;
if (!stn_included(pimg)) {
if (!skip_coord(pimg->fh)) return img_BAD;
pimg->pending = 0;
goto again3;
}
pimg->flags = (int)opt & 0x7f;
} else if ((opt >> 6) == 1) {
if (read_v8label(pimg, opt & 0x20, 0) == img_BAD) return img_BAD;
result = img_LINE;
if (!survey_included(pimg)) {
if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
pimg->pending = 15;
goto again3;
}
if (pimg->pending) {
*p = pimg->mv;
if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
pimg->pending = opt;
return img_MOVE;
}
pimg->flags = (int)opt & 0x1f;
} else {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (!read_coord(pimg->fh, p)) return img_BAD;
pimg->pending = 0;
return result;
}
static int
img_read_item_v3to7(img *pimg, img_point *p)
{
int result;
int opt;
pimg->l = pimg->r = pimg->u = pimg->d = -1.0;
if (pimg->pending == 256) {
pimg->pending = 0;
return img_XSECT_END;
}
if (pimg->pending >= 0x80) {
*p = pimg->mv;
pimg->flags = (int)(pimg->pending) & 0x3f;
pimg->pending = 0;
return img_LINE;
}
again3: /* label to goto if we get a prefix, date, or lrud */
pimg->label = pimg->label_buf;
opt = GETC(pimg->fh);
if (opt == EOF) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
switch (opt >> 6) {
case 0:
if (opt == 0) {
if (!pimg->label_len) return img_STOP; /* end of data marker */
pimg->label_len = 0;
goto again3;
}
if (opt < 15) {
/* 1-14 mean trim that many levels from current prefix */
int c;
if (pimg->label_len <= 17) {
/* zero prefix using "0" */
img_errno = IMG_BADFORMAT;
return img_BAD;
}
/* extra - 1 because label_len points to one past the end */
c = pimg->label_len - 17 - 1;
while (pimg->label_buf[c] != '.' || --opt > 0) {
if (--c < 0) {
/* zero prefix using "0" */
img_errno = IMG_BADFORMAT;
return img_BAD;
}
}
c++;
pimg->label_len = c;
goto again3;
}
if (opt == 15) {
result = img_MOVE;
break;
}
if (opt >= 0x20) {
switch (opt) {
case 0x20: /* Single date */
if (pimg->version < 7) {
int date1 = get32(pimg->fh);
#if IMG_API_VERSION == 0
pimg->date2 = pimg->date1 = date1;
#else /* IMG_API_VERSION == 1 */
if (date1 != 0) {
pimg->days2 = pimg->days1 = (date1 / 86400) + 25567;
} else {
pimg->days2 = pimg->days1 = -1;
}
#endif
} else {
int days1 = (int)getu16(pimg->fh);
#if IMG_API_VERSION == 0
pimg->date2 = pimg->date1 = (days1 - 25567) * 86400;
#else /* IMG_API_VERSION == 1 */
pimg->days2 = pimg->days1 = days1;
#endif
}
break;
case 0x21: /* Date range (short for v7+) */
if (pimg->version < 7) {
INT32_T date1 = get32(pimg->fh);
INT32_T date2 = get32(pimg->fh);
#if IMG_API_VERSION == 0
pimg->date1 = date1;
pimg->date2 = date2;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = (date1 / 86400) + 25567;
pimg->days2 = (date2 / 86400) + 25567;
#endif
} else {
int days1 = (int)getu16(pimg->fh);
int days2 = days1 + GETC(pimg->fh) + 1;
#if IMG_API_VERSION == 0
pimg->date1 = (days1 - 25567) * 86400;
pimg->date2 = (days2 - 25567) * 86400;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = days1;
pimg->days2 = days2;
#endif
}
break;
case 0x22: /* Error info */
pimg->n_legs = get32(pimg->fh);
pimg->length = get32(pimg->fh) / 100.0;
pimg->E = get32(pimg->fh) / 100.0;
pimg->H = get32(pimg->fh) / 100.0;
pimg->V = get32(pimg->fh) / 100.0;
if (feof(pimg->fh)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
return img_ERROR_INFO;
case 0x23: { /* v7+: Date range (long) */
if (pimg->version < 7) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
int days1 = (int)getu16(pimg->fh);
int days2 = (int)getu16(pimg->fh);
if (feof(pimg->fh)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
#if IMG_API_VERSION == 0
pimg->date1 = (days1 - 25567) * 86400;
pimg->date2 = (days2 - 25567) * 86400;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = days1;
pimg->days2 = days2;
#endif
break;
}
case 0x24: { /* v7+: No date info */
#if IMG_API_VERSION == 0
pimg->date1 = pimg->date2 = 0;
#else /* IMG_API_VERSION == 1 */
pimg->days1 = pimg->days2 = -1;
#endif
break;
}
case 0x30: case 0x31: /* LRUD */
case 0x32: case 0x33: /* Big LRUD! */
if (read_v3label(pimg) == img_BAD) return img_BAD;
pimg->flags = (int)opt & 0x01;
if (opt < 0x32) {
pimg->l = get16(pimg->fh) / 100.0;
pimg->r = get16(pimg->fh) / 100.0;
pimg->u = get16(pimg->fh) / 100.0;
pimg->d = get16(pimg->fh) / 100.0;
} else {
pimg->l = get32(pimg->fh) / 100.0;
pimg->r = get32(pimg->fh) / 100.0;
pimg->u = get32(pimg->fh) / 100.0;
pimg->d = get32(pimg->fh) / 100.0;
}
if (feof(pimg->fh)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
if (!stn_included(pimg)) {
return img_XSECT_END;
}
/* If this is the last cross-section in this passage, set
* pending so we return img_XSECT_END next time. */
if (pimg->flags & 0x01) {
pimg->pending = 256;
pimg->flags &= ~0x01;
}
return img_XSECT;
default: /* 0x25 - 0x2f and 0x34 - 0x3f are currently unallocated. */
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (feof(pimg->fh)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
goto again3;
}
/* 16-31 mean remove (n - 15) characters from the prefix */
/* zero prefix using 0 */
if (pimg->label_len <= (size_t)(opt - 15)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
pimg->label_len -= (opt - 15);
goto again3;
case 1:
if (read_v3label(pimg) == img_BAD) return img_BAD;
result = img_LABEL;
if (!stn_included(pimg)) {
if (!skip_coord(pimg->fh)) return img_BAD;
pimg->pending = 0;
goto again3;
}
pimg->flags = (int)opt & 0x3f;
break;
case 2:
if (read_v3label(pimg) == img_BAD) return img_BAD;
result = img_LINE;
if (!survey_included(pimg)) {
if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
pimg->pending = 15;
goto again3;
}
if (pimg->pending) {
*p = pimg->mv;
if (!read_coord(pimg->fh, &(pimg->mv))) return img_BAD;
pimg->pending = opt;
return img_MOVE;
}
pimg->flags = (int)opt & 0x3f;
break;
default:
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (!read_coord(pimg->fh, p)) return img_BAD;
pimg->pending = 0;
return result;
}
static int
img_read_item_ancient(img *pimg, img_point *p)
{
int result;
static long opt_lookahead = 0;
static img_point pt = { 0.0, 0.0, 0.0 };
long opt;
again: /* label to goto if we get a cross */
pimg->label = pimg->label_buf;
pimg->label[0] = '\0';
if (pimg->version == 1) {
if (opt_lookahead) {
opt = opt_lookahead;
opt_lookahead = 0;
} else {
opt = get32(pimg->fh);
}
} else {
opt = GETC(pimg->fh);
}
if (feof(pimg->fh)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
switch (opt) {
case -1: case 0:
return img_STOP; /* end of data marker */
case 1:
/* skip coordinates */
if (!skip_coord(pimg->fh)) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
goto again;
case 2: case 3: {
size_t len;
result = img_LABEL;
if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
if (pimg->label[0] == '\\') pimg->label++;
len = strlen(pimg->label);
if (len == 0 || pimg->label[len - 1] != '\n') {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
/* Ignore empty labels in some .3d files (caused by a bug) */
if (len == 1) goto again;
pimg->label[len - 1] = '\0';
pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
if (opt == 2) goto done;
break;
}
case 6: case 7: {
long len;
result = img_LABEL;
if (opt == 7)
pimg->flags = GETC(pimg->fh);
else
pimg->flags = img_SFLAG_UNDERGROUND; /* no flags given... */
len = get32(pimg->fh);
if (feof(pimg->fh)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
/* Ignore empty labels in some .3d files (caused by a bug) */
if (len == 0) goto again;
if (!check_label_space(pimg, len + 1)) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
if (fread(pimg->label_buf, len, 1, pimg->fh) != 1) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
pimg->label_buf[len] = '\0';
break;
}
case 4:
result = img_MOVE;
break;
case 5:
result = img_LINE;
break;
default:
switch ((int)opt & 0xc0) {
case 0x80:
pimg->flags = (int)opt & 0x3f;
result = img_LINE;
break;
case 0x40: {
char *q;
pimg->flags = (int)opt & 0x3f;
result = img_LABEL;
if (!fgets(pimg->label_buf, pimg->buf_len, pimg->fh)) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
q = pimg->label_buf + strlen(pimg->label_buf) - 1;
/* Ignore empty-labels in some .3d files (caused by a bug) */
if (q == pimg->label_buf) goto again;
if (*q != '\n') {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
*q = '\0';
break;
}
default:
img_errno = IMG_BADFORMAT;
return img_BAD;
}
break;
}
if (!read_coord(pimg->fh, &pt)) return img_BAD;
if (result == img_LABEL && !stn_included(pimg)) {
goto again;
}
done:
*p = pt;
if (result == img_MOVE && pimg->version == 1) {
/* peek at next code and see if it's an old-style label */
opt_lookahead = get32(pimg->fh);
if (feof(pimg->fh)) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
if (opt_lookahead == 2) return img_read_item_ancient(pimg, p);
}
return result;
}
static int
img_read_item_ascii_wrapper(img *pimg, img_point *p)
{
/* We need to set the default locale for fscanf() to work on
* numbers with "." as decimal point. */
int result;
char * current_locale = my_strdup(setlocale(LC_NUMERIC, NULL));
setlocale(LC_NUMERIC, "C");
result = img_read_item_ascii(pimg, p);
setlocale(LC_NUMERIC, current_locale);
free(current_locale);
return result;
}
/* Handle all ASCII formats. */
static int
img_read_item_ascii(img *pimg, img_point *p)
{
int result;
pimg->label = pimg->label_buf;
if (pimg->version == 0) {
ascii_again:
pimg->label[0] = '\0';
if (feof(pimg->fh)) return img_STOP;
if (pimg->pending) {
pimg->pending = 0;
result = img_LINE;
} else {
char cmd[7];
/* Stop if nothing found */
if (fscanf(pimg->fh, "%6s", cmd) < 1) return img_STOP;
if (strcmp(cmd, "move") == 0)
result = img_MOVE;
else if (strcmp(cmd, "draw") == 0)
result = img_LINE;
else if (strcmp(cmd, "line") == 0) {
/* set flag to indicate to process second triplet as LINE */
pimg->pending = 1;
result = img_MOVE;
} else if (strcmp(cmd, "cross") == 0) {
if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
img_errno = feof(pimg->fh) ? IMG_BADFORMAT : IMG_READERROR;
return img_BAD;
}
goto ascii_again;
} else if (strcmp(cmd, "name") == 0) {
size_t off = 0;
int ch = GETC(pimg->fh);
if (ch == ' ') ch = GETC(pimg->fh);
while (ch != ' ') {
if (ch == '\n' || ch == EOF) {
img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
return img_BAD;
}
if (off == pimg->buf_len) {
if (!check_label_space(pimg, pimg->buf_len * 2)) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
}
pimg->label_buf[off++] = ch;
ch = GETC(pimg->fh);
}
pimg->label_buf[off] = '\0';
pimg->label = pimg->label_buf;
if (pimg->label[0] == '\\') pimg->label++;
pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
result = img_LABEL;
} else {
img_errno = IMG_BADFORMAT;
return img_BAD; /* unknown keyword */
}
}
if (fscanf(pimg->fh, "%lf%lf%lf", &p->x, &p->y, &p->z) < 3) {
img_errno = ferror(pimg->fh) ? IMG_READERROR : IMG_BADFORMAT;
return img_BAD;
}
if (result == img_LABEL && !stn_included(pimg)) {
goto ascii_again;
}
return result;
} else if (pimg->version == VERSION_SURVEX_POS) {
/* Survex .pos file */
int ch;
size_t off;
pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
againpos:
while (fscanf(pimg->fh, "(%lf,%lf,%lf )", &p->x, &p->y, &p->z) != 3) {
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
if (feof(pimg->fh)) return img_STOP;
if (pimg->pending) {
img_errno = IMG_BADFORMAT;
return img_BAD;
}
pimg->pending = 1;
/* ignore rest of line */
do {
ch = GETC(pimg->fh);
} while (ch != '\n' && ch != '\r' && ch != EOF);
}
pimg->label_buf[0] = '\0';
do {
ch = GETC(pimg->fh);
} while (ch == ' ' || ch == '\t');
if (ch == '\n' || ch == EOF) {
/* If there's no label, set img_SFLAG_ANON. */
pimg->flags |= img_SFLAG_ANON;
return img_LABEL;
}
pimg->label_buf[0] = ch;
off = 1;
while (!feof(pimg->fh)) {
if (!fgets(pimg->label_buf + off, pimg->buf_len - off, pimg->fh)) {
img_errno = IMG_READERROR;
return img_BAD;
}
off += strlen(pimg->label_buf + off);
if (off && pimg->label_buf[off - 1] == '\n') {
pimg->label_buf[off - 1] = '\0';
break;
}
if (!check_label_space(pimg, pimg->buf_len * 2)) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
}
pimg->label = pimg->label_buf;
if (pimg->label[0] == '\\') pimg->label++;
if (!stn_included(pimg)) goto againpos;
return img_LABEL;
} else if (pimg->version == VERSION_COMPASS_PLT) {
/* Compass .plt file */
if (pimg->pending > 0) {
/* -1 signals we've entered the first survey we want to
* read, and need to fudge lots if the first action is 'D'...
*/
/* pending MOVE or LINE */
int r = pimg->pending - 4;
pimg->pending = 0;
pimg->flags = 0;
pimg->label[pimg->label_len] = '\0';
return r;
}
while (1) {
char *line;
char *q;
size_t len = 0;
int ch = GETC(pimg->fh);
switch (ch) {
case '\x1a': case EOF: /* Don't insist on ^Z at end of file */
return img_STOP;
case 'X': case 'F': case 'S':
/* bounding boX (marks end of survey), Feature survey, or
* new Section - skip to next survey */
if (pimg->survey) return img_STOP;
skip_to_N:
while (1) {
do {
ch = GETC(pimg->fh);
} while (ch != '\n' && ch != '\r' && ch != EOF);
while (ch == '\n' || ch == '\r') ch = GETC(pimg->fh);
if (ch == 'N') break;
if (ch == '\x1a' || ch == EOF) return img_STOP;
}
/* FALLTHRU */
case 'N':
line = getline_alloc(pimg->fh);
if (!line) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
while (line[len] > 32) ++len;
if (pimg->label_len == 0) pimg->pending = -1;
if (!check_label_space(pimg, len + 1)) {
osfree(line);
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
pimg->label_len = len;
pimg->label = pimg->label_buf;
memcpy(pimg->label, line, len);
pimg->label[len] = '\0';
osfree(line);
break;
case 'M': case 'D': {
/* Move or Draw */
long fpos = -1;
if (pimg->survey && pimg->label_len == 0) {
/* We're only holding onto this line in case the first line
* of the 'N' is a 'D', so skip it for now...
*/
goto skip_to_N;
}
if (ch == 'D' && pimg->pending == -1) {
if (pimg->survey) {
fpos = ftell(pimg->fh) - 1;
fseek(pimg->fh, pimg->start, SEEK_SET);
ch = GETC(pimg->fh);
pimg->pending = 0;
} else {
/* If a file actually has a 'D' before any 'M', then
* pretend the 'D' is an 'M' - one of the examples
* in the docs was like this! */
ch = 'M';
}
}
line = getline_alloc(pimg->fh);
if (!line) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
/* Compass stores coordinates as North, East, Up = (y,x,z)! */
if (sscanf(line, "%lf%lf%lf", &p->y, &p->x, &p->z) != 3) {
osfree(line);
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
} else {
img_errno = IMG_BADFORMAT;
}
return img_BAD;
}
p->x *= METRES_PER_FOOT;
p->y *= METRES_PER_FOOT;
p->z *= METRES_PER_FOOT;
q = strchr(line, 'S');
if (!q) {
osfree(line);
img_errno = IMG_BADFORMAT;
return img_BAD;
}
++q;
len = 0;
while (q[len] > ' ') ++len;
q[len] = '\0';
len += 2; /* ' ' and '\0' */
if (!check_label_space(pimg, pimg->label_len + len)) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
pimg->label = pimg->label_buf;
if (pimg->label_len) {
pimg->label[pimg->label_len] = ' ';
memcpy(pimg->label + pimg->label_len + 1, q, len - 1);
} else {
memcpy(pimg->label, q, len - 1);
}
q += len - 1;
/* Now read LRUD. Technically, this is optional but virtually
* all PLT files have it (with dummy negative values if no LRUD
* was measured) and some versions of Compass can't read PLT
* files without it!
*/
while (*q && *q <= ' ') q++;
if (*q == 'P') {
if (sscanf(q + 1, "%lf%lf%lf%lf",
&pimg->l, &pimg->r, &pimg->u, &pimg->d) != 4) {
osfree(line);
if (ferror(pimg->fh)) {
img_errno = IMG_READERROR;
} else {
img_errno = IMG_BADFORMAT;
}
return img_BAD;
}
pimg->l *= METRES_PER_FOOT;
pimg->r *= METRES_PER_FOOT;
pimg->u *= METRES_PER_FOOT;
pimg->d *= METRES_PER_FOOT;
} else {
pimg->l = pimg->r = pimg->u = pimg->d = -1;
}
osfree(line);
pimg->flags = img_SFLAG_UNDERGROUND; /* default flags */
if (fpos != -1) {
fseek(pimg->fh, fpos, SEEK_SET);
} else {
pimg->pending = (ch == 'M' ? img_MOVE : img_LINE) + 4;
}
return img_LABEL;
}
default:
img_errno = IMG_BADFORMAT;
return img_BAD;
}
}
} else {
/* CMAP .xyz file */
char *line = NULL;
char *q;
size_t len;
if (pimg->pending) {
/* pending MOVE or LINE or LABEL or STOP */
int r = pimg->pending - 4;
/* Set label to empty - don't use "" as we adjust label relative
* to label_buf when label_buf is reallocated. */
pimg->label = pimg->label_buf + strlen(pimg->label_buf);
pimg->flags = 0;
if (r == img_LABEL) {
/* nasty magic */
read_xyz_shot_coords(p, pimg->label_buf + 16);
subtract_xyz_shot_deltas(p, pimg->label_buf + 16);
pimg->pending = img_STOP + 4;
return img_MOVE;
}
pimg->pending = 0;
if (r == img_STOP) {
/* nasty magic */
read_xyz_shot_coords(p, pimg->label_buf + 16);
return img_LINE;
}
return r;
}
pimg->label = pimg->label_buf;
do {
osfree(line);
if (feof(pimg->fh)) return img_STOP;
line = getline_alloc(pimg->fh);
if (!line) {
img_errno = IMG_OUTOFMEMORY;
return img_BAD;
}
} while (line[0] == ' ' || line[0] == '\0');
if (line[0] == '\x1a') return img_STOP;
len = strlen(line);
if (pimg->version == VERSION_CMAP_STATION) {
/* station variant */
if (len < 37) {
osfree(line);
img_errno = IMG_BADFORMAT;
return img_BAD;
}
memcpy(pimg->label, line, 6);
q = (char *)memchr(pimg->label, ' ', 6);
if (!q) q = pimg->label + 6;
*q = '\0';
read_xyz_station_coords(p, line);
/* FIXME: look at prev for lines (line + 32, 5) */
/* FIXME: duplicate stations... */
return img_LABEL;
} else {
/* Shot variant (VERSION_CMAP_SHOT) */
char old[8], new_[8];
if (len < 61) {
osfree(line);
img_errno = IMG_BADFORMAT;
return img_BAD;
}
memcpy(old, line, 7);
q = (char *)memchr(old, ' ', 7);
if (!q) q = old + 7;
*q = '\0';
memcpy(new_, line + 7, 7);
q = (char *)memchr(new_, ' ', 7);
if (!q) q = new_ + 7;
*q = '\0';
pimg->flags = img_SFLAG_UNDERGROUND;
if (strcmp(old, new_) == 0) {
pimg->pending = img_MOVE + 4;
read_xyz_shot_coords(p, line);
strcpy(pimg->label, new_);
osfree(line);
return img_LABEL;
}
if (strcmp(old, pimg->label) == 0) {
pimg->pending = img_LINE + 4;
read_xyz_shot_coords(p, line);
strcpy(pimg->label, new_);
osfree(line);
return img_LABEL;
}
pimg->pending = img_LABEL + 4;
read_xyz_shot_coords(p, line);
strcpy(pimg->label, new_);
memcpy(pimg->label + 16, line, 70);
osfree(line);
return img_LABEL;
}
}
}
static void
write_coord(FILE *fh, double x, double y, double z)
{
SVX_ASSERT(fh);
/* Output in cm */
static INT32_T X_, Y_, Z_;
INT32_T X = my_lround(x * 100.0);
INT32_T Y = my_lround(y * 100.0);
INT32_T Z = my_lround(z * 100.0);
X_ -= X;
Y_ -= Y;
Z_ -= Z;
put32(X, fh);
put32(Y, fh);
put32(Z, fh);
X_ = X; Y_ = Y; Z_ = Z;
}
static int
write_v3label(img *pimg, int opt, const char *s)
{
size_t len, n, dot;
/* find length of common prefix */
dot = 0;
for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
if (s[len] == '.') dot = len + 1;
}
SVX_ASSERT(len <= pimg->label_len);
n = pimg->label_len - len;
if (len == 0) {
if (pimg->label_len) PUTC(0, pimg->fh);
} else if (n <= 16) {
if (n) PUTC(n + 15, pimg->fh);
} else if (dot == 0) {
if (pimg->label_len) PUTC(0, pimg->fh);
len = 0;
} else {
const char *p = pimg->label_buf + dot;
n = 1;
for (len = pimg->label_len - dot - 17; len; len--) {
if (*p++ == '.') n++;
}
if (n <= 14) {
PUTC(n, pimg->fh);
len = dot;
} else {
if (pimg->label_len) PUTC(0, pimg->fh);
len = 0;
}
}
n = strlen(s + len);
PUTC(opt, pimg->fh);
if (n < 0xfe) {
PUTC(n, pimg->fh);
} else if (n < 0xffff + 0xfe) {
PUTC(0xfe, pimg->fh);
put16((short)(n - 0xfe), pimg->fh);
} else {
PUTC(0xff, pimg->fh);
put32(n, pimg->fh);
}
fwrite(s + len, n, 1, pimg->fh);
n += len;
pimg->label_len = n;
if (!check_label_space(pimg, n + 1))
return 0; /* FIXME: distinguish out of memory... */
memcpy(pimg->label_buf + len, s + len, n - len + 1);
return !ferror(pimg->fh);
}
static int
write_v8label(img *pimg, int opt, int common_flag, size_t common_val,
const char *s)
{
size_t len, del, add;
/* find length of common prefix */
for (len = 0; s[len] == pimg->label_buf[len] && s[len] != '\0'; len++) {
}
SVX_ASSERT(len <= pimg->label_len);
del = pimg->label_len - len;
add = strlen(s + len);
if (add == common_val && del == common_val) {
PUTC(opt | common_flag, pimg->fh);
} else {
PUTC(opt, pimg->fh);
if (del <= 15 && add <= 15 && (del || add)) {
PUTC((del << 4) | add, pimg->fh);
} else {
PUTC(0x00, pimg->fh);
if (del < 0xff) {
PUTC(del, pimg->fh);
} else {
PUTC(0xff, pimg->fh);
put32(del, pimg->fh);
}
if (add < 0xff) {
PUTC(add, pimg->fh);
} else {
PUTC(0xff, pimg->fh);
put32(add, pimg->fh);
}
}
}
if (add)
fwrite(s + len, add, 1, pimg->fh);
pimg->label_len = len + add;
if (add > del && !check_label_space(pimg, pimg->label_len + 1))
return 0; /* FIXME: distinguish out of memory... */
memcpy(pimg->label_buf + len, s + len, add + 1);
return !ferror(pimg->fh);
}
static void
img_write_item_date_new(img *pimg)
{
int same, unset;
/* Only write dates when they've changed. */
#if IMG_API_VERSION == 0
if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
return;
same = (pimg->date1 == pimg->date2);
unset = (pimg->date1 == 0);
#else /* IMG_API_VERSION == 1 */
if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
return;
same = (pimg->days1 == pimg->days2);
unset = (pimg->days1 == -1);
#endif
if (same) {
if (unset) {
PUTC(0x10, pimg->fh);
} else {
PUTC(0x11, pimg->fh);
#if IMG_API_VERSION == 0
put16(pimg->date1 / 86400 + 25567, pimg->fh);
#else /* IMG_API_VERSION == 1 */
put16(pimg->days1, pimg->fh);
#endif
}
} else {
#if IMG_API_VERSION == 0
int diff = (pimg->date2 - pimg->date1) / 86400;
if (diff > 0 && diff <= 256) {
PUTC(0x12, pimg->fh);
put16(pimg->date1 / 86400 + 25567, pimg->fh);
PUTC(diff - 1, pimg->fh);
} else {
PUTC(0x13, pimg->fh);
put16(pimg->date1 / 86400 + 25567, pimg->fh);
put16(pimg->date2 / 86400 + 25567, pimg->fh);
}
#else /* IMG_API_VERSION == 1 */
int diff = pimg->days2 - pimg->days1;
if (diff > 0 && diff <= 256) {
PUTC(0x12, pimg->fh);
put16(pimg->days1, pimg->fh);
PUTC(diff - 1, pimg->fh);
} else {
PUTC(0x13, pimg->fh);
put16(pimg->days1, pimg->fh);
put16(pimg->days2, pimg->fh);
}
#endif
}
#if IMG_API_VERSION == 0
pimg->olddate1 = pimg->date1;
pimg->olddate2 = pimg->date2;
#else /* IMG_API_VERSION == 1 */
pimg->olddays1 = pimg->days1;
pimg->olddays2 = pimg->days2;
#endif
}
static void
img_write_item_date(img *pimg)
{
int same, unset;
/* Only write dates when they've changed. */
#if IMG_API_VERSION == 0
if (pimg->date1 == pimg->olddate1 && pimg->date2 == pimg->olddate2)
return;
same = (pimg->date1 == pimg->date2);
unset = (pimg->date1 == 0);
#else /* IMG_API_VERSION == 1 */
if (pimg->days1 == pimg->olddays1 && pimg->days2 == pimg->olddays2)
return;
same = (pimg->days1 == pimg->days2);
unset = (pimg->days1 == -1);
#endif
if (same) {
if (img_output_version < 7) {
PUTC(0x20, pimg->fh);
#if IMG_API_VERSION == 0
put32(pimg->date1, pimg->fh);
#else /* IMG_API_VERSION == 1 */
put32((pimg->days1 - 25567) * 86400, pimg->fh);
#endif
} else {
if (unset) {
PUTC(0x24, pimg->fh);
} else {
PUTC(0x20, pimg->fh);
#if IMG_API_VERSION == 0
put16(pimg->date1 / 86400 + 25567, pimg->fh);
#else /* IMG_API_VERSION == 1 */
put16(pimg->days1, pimg->fh);
#endif
}
}
} else {
if (img_output_version < 7) {
PUTC(0x21, pimg->fh);
#if IMG_API_VERSION == 0
put32(pimg->date1, pimg->fh);
put32(pimg->date2, pimg->fh);
#else /* IMG_API_VERSION == 1 */
put32((pimg->days1 - 25567) * 86400, pimg->fh);
put32((pimg->days2 - 25567) * 86400, pimg->fh);
#endif
} else {
#if IMG_API_VERSION == 0
int diff = (pimg->date2 - pimg->date1) / 86400;
if (diff > 0 && diff <= 256) {
PUTC(0x21, pimg->fh);
put16(pimg->date1 / 86400 + 25567, pimg->fh);
PUTC(diff - 1, pimg->fh);
} else {
PUTC(0x23, pimg->fh);
put16(pimg->date1 / 86400 + 25567, pimg->fh);
put16(pimg->date2 / 86400 + 25567, pimg->fh);
}
#else /* IMG_API_VERSION == 1 */
int diff = pimg->days2 - pimg->days1;
if (diff > 0 && diff <= 256) {
PUTC(0x21, pimg->fh);
put16(pimg->days1, pimg->fh);
PUTC(diff - 1, pimg->fh);
} else {
PUTC(0x23, pimg->fh);
put16(pimg->days1, pimg->fh);
put16(pimg->days2, pimg->fh);
}
#endif
}
}
#if IMG_API_VERSION == 0
pimg->olddate1 = pimg->date1;
pimg->olddate2 = pimg->date2;
#else /* IMG_API_VERSION == 1 */
pimg->olddays1 = pimg->days1;
pimg->olddays2 = pimg->days2;
#endif
}
static void
img_write_item_new(img *pimg, int code, int flags, const char *s,
double x, double y, double z);
static void
img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
double x, double y, double z);
static void
img_write_item_ancient(img *pimg, int code, int flags, const char *s,
double x, double y, double z);
void
img_write_item(img *pimg, int code, int flags, const char *s,
double x, double y, double z)
{
if (!pimg) return;
if (pimg->version >= 8) {
img_write_item_new(pimg, code, flags, s, x, y, z);
} else if (pimg->version >= 3) {
img_write_item_v3to7(pimg, code, flags, s, x, y, z);
} else {
img_write_item_ancient(pimg, code, flags, s, x, y, z);
}
}
static void
img_write_item_new(img *pimg, int code, int flags, const char *s,
double x, double y, double z)
{
switch (code) {
case img_LABEL:
write_v8label(pimg, 0x80 | flags, 0, -1, s);
break;
case img_XSECT: {
INT32_T l, r, u, d, max_dim;
img_write_item_date_new(pimg);
l = (INT32_T)my_lround(pimg->l * 100.0);
r = (INT32_T)my_lround(pimg->r * 100.0);
u = (INT32_T)my_lround(pimg->u * 100.0);
d = (INT32_T)my_lround(pimg->d * 100.0);
if (l < 0) l = -1;
if (r < 0) r = -1;
if (u < 0) u = -1;
if (d < 0) d = -1;
max_dim = max(max(l, r), max(u, d));
flags = (flags & img_XFLAG_END) ? 1 : 0;
if (max_dim >= 32768) flags |= 2;
write_v8label(pimg, 0x30 | flags, 0, -1, s);
if (flags & 2) {
/* Big passage! Need to use 4 bytes. */
put32(l, pimg->fh);
put32(r, pimg->fh);
put32(u, pimg->fh);
put32(d, pimg->fh);
} else {
put16(l, pimg->fh);
put16(r, pimg->fh);
put16(u, pimg->fh);
put16(d, pimg->fh);
}
return;
}
case img_MOVE:
PUTC(15, pimg->fh);
break;
case img_LINE:
img_write_item_date_new(pimg);
if (pimg->style != pimg->oldstyle) {
switch (pimg->style) {
case img_STYLE_NORMAL:
case img_STYLE_DIVING:
case img_STYLE_CARTESIAN:
case img_STYLE_CYLPOLAR:
case img_STYLE_NOSURVEY:
PUTC(pimg->style, pimg->fh);
break;
}
pimg->oldstyle = pimg->style;
}
write_v8label(pimg, 0x40 | flags, 0x20, 0x00, s ? s : "");
break;
default: /* ignore for now */
return;
}
write_coord(pimg->fh, x, y, z);
}
static void
img_write_item_v3to7(img *pimg, int code, int flags, const char *s,
double x, double y, double z)
{
switch (code) {
case img_LABEL:
write_v3label(pimg, 0x40 | flags, s);
break;
case img_XSECT: {
INT32_T l, r, u, d, max_dim;
/* Need at least version 5 for img_XSECT. */
if (pimg->version < 5) return;
img_write_item_date(pimg);
l = (INT32_T)my_lround(pimg->l * 100.0);
r = (INT32_T)my_lround(pimg->r * 100.0);
u = (INT32_T)my_lround(pimg->u * 100.0);
d = (INT32_T)my_lround(pimg->d * 100.0);
if (l < 0) l = -1;
if (r < 0) r = -1;
if (u < 0) u = -1;
if (d < 0) d = -1;
max_dim = max(max(l, r), max(u, d));
flags = (flags & img_XFLAG_END) ? 1 : 0;
if (max_dim >= 32768) flags |= 2;
write_v3label(pimg, 0x30 | flags, s);
if (flags & 2) {
/* Big passage! Need to use 4 bytes. */
put32(l, pimg->fh);
put32(r, pimg->fh);
put32(u, pimg->fh);
put32(d, pimg->fh);
} else {
put16(l, pimg->fh);
put16(r, pimg->fh);
put16(u, pimg->fh);
put16(d, pimg->fh);
}
return;
}
case img_MOVE:
PUTC(15, pimg->fh);
break;
case img_LINE:
if (pimg->version >= 4) {
img_write_item_date(pimg);
}
write_v3label(pimg, 0x80 | flags, s ? s : "");
break;
default: /* ignore for now */
return;
}
write_coord(pimg->fh, x, y, z);
}
static void
img_write_item_ancient(img *pimg, int code, int flags, const char *s,
double x, double y, double z)
{
size_t len;
INT32_T opt = 0;
SVX_ASSERT(pimg->version > 0);
switch (code) {
case img_LABEL:
if (pimg->version == 1) {
/* put a move before each label */
img_write_item_ancient(pimg, img_MOVE, 0, NULL, x, y, z);
put32(2, pimg->fh);
fputsnl(s, pimg->fh);
return;
}
len = strlen(s);
if (len > 255 || strchr(s, '\n')) {
/* long label - not in early incarnations of v2 format, but few
* 3d files will need these, so better not to force incompatibility
* with a new version I think... */
PUTC(7, pimg->fh);
PUTC(flags, pimg->fh);
put32(len, pimg->fh);
fputs(s, pimg->fh);
} else {
PUTC(0x40 | (flags & 0x3f), pimg->fh);
fputsnl(s, pimg->fh);
}
opt = 0;
break;
case img_MOVE:
opt = 4;
break;
case img_LINE:
if (pimg->version > 1) {
opt = 0x80 | (flags & 0x3f);
break;
}
opt = 5;
break;
default: /* ignore for now */
return;
}
if (pimg->version == 1) {
put32(opt, pimg->fh);
} else {
if (opt) PUTC(opt, pimg->fh);
}
write_coord(pimg->fh, x, y, z);
}
/* Write error information for the current traverse
* n_legs is the number of legs in the traverse
* length is the traverse length (in m)
* E is the ratio of the observed misclosure to the theoretical one
* H is the ratio of the observed horizontal misclosure to the theoretical one
* V is the ratio of the observed vertical misclosure to the theoretical one
*/
void
img_write_errors(img *pimg, int n_legs, double length,
double E, double H, double V)
{
PUTC((pimg->version >= 8 ? 0x1f : 0x22), pimg->fh);
put32(n_legs, pimg->fh);
put32((INT32_T)my_lround(length * 100.0), pimg->fh);
put32((INT32_T)my_lround(E * 100.0), pimg->fh);
put32((INT32_T)my_lround(H * 100.0), pimg->fh);
put32((INT32_T)my_lround(V * 100.0), pimg->fh);
}
int
img_close(img *pimg)
{
int result = 1;
if (pimg) {
if (pimg->fh) {
if (pimg->fRead) {
osfree(pimg->survey);
osfree(pimg->title);
osfree(pimg->cs);
osfree(pimg->datestamp);
} else {
/* write end of data marker */
switch (pimg->version) {
case 1:
put32((INT32_T)-1, pimg->fh);
break;
default:
if (pimg->version <= 7 ?
(pimg->label_len != 0) :
(pimg->style != img_STYLE_NORMAL)) {
PUTC(0, pimg->fh);
}
/* FALL THROUGH */
case 2:
PUTC(0, pimg->fh);
break;
}
}
if (ferror(pimg->fh)) result = 0;
if (pimg->close_func && pimg->close_func(pimg->fh))
result = 0;
if (!result) img_errno = pimg->fRead ? IMG_READERROR : IMG_WRITEERROR;
}
osfree(pimg->label_buf);
osfree(pimg->filename_opened);
osfree(pimg);
}
return result;
}