From 6d56a7e13deb48379e587250bc23e043b7c154db Mon Sep 17 00:00:00 2001 From: Paul Walko Date: Fri, 10 Dec 2021 10:24:58 -0500 Subject: [PATCH] init commit --- .gitignore | 1 + README.md | 17 + build.sh | 8 + img.c | 2851 ++++++++++++++++++++++++++++++++++++++++++++++++++ img.h | 420 ++++++++ img.js | 1 + img.wasm | Bin 0 -> 59773 bytes img_shim.c | 62 ++ index.js | 214 ++++ package.json | 11 + yarn.lock | 41 + 11 files changed, 3626 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 build.sh create mode 100644 img.c create mode 100644 img.h create mode 100644 img.js create mode 100755 img.wasm create mode 100644 img_shim.c create mode 100644 index.js create mode 100644 package.json create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f714db --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +Example + +``` +const survex = require('survex.js'); + +survex.load((Survex) => { + Survex.imgOpen('http://10.42.0.203:8081/svn/skydusky/us/va/bland/Skydusky/Skydusky.3d') + .then(img => { + while (img.code >= 0) { + if (img.code === survex.codes['raw']['img_LABEL']) { + console.log(`label: ${img.label}, X: ${img.mv.x}, Y: ${img.mv.y}, Z: ${img.mv.z}`); + } + img = Survex.imgReadItem(img.ptr); + } + }); +}); +``` diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..dd32327 --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +emcc img.c img_shim.c \ + -O3 \ + -s EXPORTED_FUNCTIONS="['_free']" \ + -s EXPORTED_RUNTIME_METHODS="['FS', 'getValue', 'UTF8ToString']" \ + -o img.js +# -s FORCE_FILESYSTEM=1 \ diff --git a/img.c b/img.c new file mode 100644 index 0000000..fa21df3 --- /dev/null +++ b/img.c @@ -0,0 +1,2851 @@ +/* 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +# define INT32_T int32_t +# define UINT32_T uint32_t +# else +# include +# 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 +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 + +#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) { + char * cs = title + real_len + 1; + if (memcmp(cs, "+init=", 6) == 0) { + /* PROJ 5 and later don't handle +init=esri: but + * that's what cavern used to put in .3d files for + * coordinate systems specified using ESRI codes. We parse + * and convert the strings cavern used to generate and + * convert to the form ESRI: which is still + * understood. + * + * PROJ 6 and later don't recognise +init=epsg: + * by default and don't apply datum shift terms in some + * cases, so we also convert these to the form + * EPSG:. + */ + char * p = cs + 6; + if (p[4] == ':' && isdigit((unsigned char)p[5]) && + ((memcmp(p, "epsg", 4) == 0 || memcmp(p, "esri", 4) == 0))) { + p = p + 6; + while (isdigit((unsigned char)*p)) { + ++p; + } + /* Allow +no_defs to be omitted as it seems to not + * actually do anything with recent PROJ - cavern always + * included it, but other software generating 3d files + * may not. + */ + if (*p == '\0' || strcmp(p, " +no_defs") == 0) { + int i; + cs = cs + 6; + for (i = 0; i < 4; ++i) { + cs[i] = toupper(cs[i]); + } + *p = '\0'; + } + } + } else if (memcmp(cs, "+proj=", 6) == 0) { + /* Convert S_MERC and UTM proj strings which cavern used + * to generate to their corresponding EPSG: codes. + */ + char * p = cs + 6; + if (memcmp(p, "utm +ellps=WGS84 +datum=WGS84 +units=m +zone=", 45) == 0) { + int n = 0; + p += 45; + while (isdigit((unsigned char)*p)) { + n = n * 10 + (*p - '0'); + ++p; + } + if (memcmp(p, " +south", 7) == 0) { + p += 7; + n += 32700; + } else { + n += 32600; + } + /* Allow +no_defs to be omitted as it seems to not + * actually do anything with recent PROJ - cavern always + * included it, but other software generating 3d files + * may not have. + */ + if (*p == '\0' || strcmp(p, " +no_defs") == 0) { + sprintf(cs, "EPSG:%d", n); + } + } else if (memcmp(p, "merc +lat_ts=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +units=m +nadgrids=@null", 89) == 0) { + p = p + 89; + /* Allow +no_defs to be omitted as it seems to not + * actually do anything with recent PROJ - cavern always + * included it, but other software generating 3d files + * may not have. + */ + if (*p == '\0' || strcmp(p, " +no_defs") == 0) { + strcpy(cs, "EPSG:3857"); + } + } + } + pimg->cs = my_strdup(cs); + } + } + 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; +} diff --git a/img.h b/img.h new file mode 100644 index 0000000..9e0a344 --- /dev/null +++ b/img.h @@ -0,0 +1,420 @@ +/* img.h + * Header file for routines to read and write processed survey data files + * + * These routines support reading processed survey data in a variety of formats + * - currently: + * + * - Survex ".3d" image files + * - Survex ".pos" files + * - Compass Plot files (".plt" and ".plf") + * - CMAP XYZ files (".sht", ".adj", ".una", ".xyz") + * + * Writing Survex ".3d" image files is supported. + * + * Copyright (C) Olly Betts 1993,1994,1997,2001,2002,2003,2004,2005,2006,2010,2011,2012,2013,2014,2016,2018 + * + * 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 + */ + +#ifndef IMG_H +# define IMG_H + +/* Define IMG_API_VERSION if you want more recent versions of the img API. + * + * 0 (default) The old API. date1 and date2 give the survey date as time_t. + * Set to 0 for "unknown". + * 1 days1 and days2 give survey dates as days since 1st Jan 1900. + * Set to -1 for "unknown". + */ +#ifndef IMG_API_VERSION +# define IMG_API_VERSION 0 +#elif IMG_API_VERSION > 1 +# error IMG_API_VERSION > 1 too new +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include /* for time_t */ + +# define img_BAD -2 +# define img_STOP -1 +# define img_MOVE 0 +# define img_LINE 1 +/* NB: img_CROSS is never output and ignored on input. + * Put crosses where labels are. */ +/* # define img_CROSS 2 */ +# define img_LABEL 3 +# define img_XSECT 4 +# define img_XSECT_END 5 +/* Loop closure information for the *preceding* traverse (img_MOVE + one or + * more img_LINEs). */ +# define img_ERROR_INFO 6 + +/* Leg flags */ +# define img_FLAG_SURFACE 0x01 +# define img_FLAG_DUPLICATE 0x02 +# define img_FLAG_SPLAY 0x04 + +/* Station flags */ +# define img_SFLAG_SURFACE 0x01 +# define img_SFLAG_UNDERGROUND 0x02 +# define img_SFLAG_ENTRANCE 0x04 +# define img_SFLAG_EXPORTED 0x08 +# define img_SFLAG_FIXED 0x10 +# define img_SFLAG_ANON 0x20 +# define img_SFLAG_WALL 0x40 + +/* File-wide flags */ +# define img_FFLAG_EXTENDED 0x80 + +/* When writing img_XSECT, img_XFLAG_END in pimg->flags means this is the last + * img_XSECT in this tube: + */ +# define img_XFLAG_END 0x01 + +# define img_STYLE_UNKNOWN -1 +# define img_STYLE_NORMAL 0 +# define img_STYLE_DIVING 1 +# define img_STYLE_CARTESIAN 2 +# define img_STYLE_CYLPOLAR 3 +# define img_STYLE_NOSURVEY 4 + +/* 3D coordinates (in metres) */ +typedef struct { + double x, y, z; +} img_point; + +typedef struct { + /* Members you can access when reading (don't touch when writing): */ + char *label; + int flags; + char *title; + /* This contains a string describing the coordinate system which the data is + * in, suitable for passing to PROJ. For a coordinate system with an EPSG + * code this will typically be "EPSG:" followed by the code number. + * + * If no coordinate system was specified then this member will be NULL. + */ + char *cs; + /* Older .3d format versions stored a human readable datestamp string. + * Format versions >= 8 versions store a string consisting of "@" followed + * by the number of seconds since midnight UTC on 1/1/1970. Some foreign + * formats contain a human readable string, others no date information + * (which results in "?" being returned). + */ + char *datestamp; + /* The datestamp as a time_t (or (time_t)-1 if not available). + * + * For 3d format versions >= 8, this is a reliable value and in UTC. Older + * 3d format versions store a human readable time, which img will attempt + * to decode, but it may fail, particularly with handling timezones. Even + * if it does work, beware that times in local time where DST applies are + * inherently ambiguous around when the clocks go back. + * + * CMAP XYZ files contain a timestamp. It's probably in localtime (but + * without any timezone information) and the example files are all pre-2000 + * and have two digit years. We do our best to turn these into a useful + * time_t value. + */ + time_t datestamp_numeric; + char separator; /* character used to separate survey levels ('.' usually) */ + + /* Members that can be set when writing: */ +#if IMG_API_VERSION == 0 + time_t date1, date2; +#else /* IMG_API_VERSION == 1 */ + int days1, days2; +#endif + double l, r, u, d; + + /* Error information - valid when img_ERROR_INFO is returned: */ + int n_legs; + double length; + double E, H, V; + + /* The filename actually opened (e.g. may have ".3d" added). + * + * This is only set if img opened the filename - if an existing stream + * is used (via img_read_stream() or similar) then this member will be + * NULL. + */ + char * filename_opened; + + /* Non-zero if reading an extended elevation: */ + int is_extended_elevation; + + /* Members that can be set when writing: */ + /* The style of the data - one of the img_STYLE_* constants above */ + int style; + + /* All other members are for internal use only: */ + FILE *fh; /* file handle of image file */ + int (*close_func)(FILE*); + char *label_buf; + size_t buf_len; + size_t label_len; + int fRead; /* 1 for reading, 0 for writing */ + long start; + /* version of file format: + * -4 => CMAP .xyz file, shot format + * -3 => CMAP .xyz file, station format + * -2 => Compass .plt file + * -1 => .pos file + * 0 => 0.01 ascii + * 1 => 0.01 binary, + * 2 => byte actions and flags + * 3 => prefixes for legs; compressed prefixes + * 4 => survey date + * 5 => LRUD info + * 6 => error info + * 7 => more compact dates with wider range + * 8 => lots of changes + */ + int version; + char *survey; + size_t survey_len; + int pending; /* for old style text format files and survey filtering */ + img_point mv; +#if IMG_API_VERSION == 0 + time_t olddate1, olddate2; +#else /* IMG_API_VERSION == 1 */ + int olddays1, olddays2; +#endif + int oldstyle; +} img; + +/* Which version of the file format to output (defaults to newest) */ +extern unsigned int img_output_version; + +/* Minimum supported value for img_output_version: */ +#define IMG_VERSION_MIN 1 + +/* Maximum supported value for img_output_version: */ +#define IMG_VERSION_MAX 8 + +/* Open a processed survey data file for reading + * + * fnm is the filename + * + * Returns pointer to an img struct or NULL + */ +#define img_open(F) img_open_survey((F), NULL) + +/* Open a processed survey data file for reading + * + * fnm is the filename + * + * survey points to a survey name to restrict reading to (or NULL for all + * survey data in the file) + * + * Returns pointer to an img struct or NULL + */ +img *img_open_survey(const char *fnm, const char *survey); + +/* Read processed survey data from an existing stream. + * + * stream is a FILE* open on the stream (can be NULL which will give error + * IMG_FILENOTFOUND so you don't need to handle that case specially). The + * stream should be opened for reading in binary mode and positioned at the + * start of the survey data file. + * + * close_func is a function to call to close the stream (most commonly + * fclose, or pclose if the stream was opened using popen()) or NULL if + * the caller wants to take care of closing the stream. + * + * filename is used to determine the format based on the file extension, + * and also the leafname with the extension removed is used for the survey + * title for formats which don't support a title or when no title is + * specified. If you're not interested in default titles, you can just + * pass the extension including a leading "." - e.g. ".3d". May not be + * NULL. + * + * Returns pointer to an img struct or NULL on error. Any close function + * specified is called on error (unless stream is NULL). + */ +#define img_read_stream(S, C, F) img_read_stream_survey((S), (C), (F), NULL) + +/* Read processed survey data from an existing stream. + * + * stream is a FILE* open on the stream (can be NULL which will give error + * IMG_FILENOTFOUND so you don't need to handle that case specially). The + * stream should be opened for reading in binary mode and positioned at the + * start of the survey data file. + * + * close_func is a function to call to close the stream (most commonly + * fclose, or pclose if the stream was opened using popen()) or NULL if + * the caller wants to take care of closing the stream. + * + * filename is used to determine the format based on the file extension, + * and also the leafname with the extension removed is used for the survey + * title for formats which don't support a title or when no title is + * specified. If you're not interested in default titles, you can just + * pass the extension including a leading "." - e.g. ".3d". filename must + * not be NULL. + * + * survey points to a survey name to restrict reading to (or NULL for all + * survey data in the file) + * + * Returns pointer to an img struct or NULL on error. Any close function + * specified is called on error. + */ +img *img_read_stream_survey(FILE *stream, int (*close_func)(FILE*), + const char *filename, + const char *survey); + +/* Open a .3d file for output with no specified coordinate system + * + * This is a very thin wrapper around img_open_write_cs() which passes NULL for + * cs, provided for compatibility with the API provided before support for + * coordinate systems was added. + * + * See img_open_write_cs() for documentation. + */ +#define img_open_write(F, T, S) img_open_write_cs(F, T, NULL, S) + +/* Open a .3d file for output in a specified coordinate system + * + * fnm is the filename + * + * title is the title + * + * cs is a string describing the coordinate system, suitable for passing to + * PROJ (or NULL to not specify a coordinate system). For a coordinate system + * with an assigned EPSG code number, "EPSG:" followed by the code number is + * the recommended way to specify this. + * + * flags contains a bitwise-or of any file-wide flags - currently only one + * is available: img_FFLAG_EXTENDED. + * + * Returns pointer to an img struct or NULL for error (check img_error() + * for details) + */ +img *img_open_write_cs(const char *fnm, const char *title, const char * cs, + int flags); + +/* Write a .3d file to a stream + * + * stream is a FILE* open on the stream (can be NULL which will give error + * IMG_FILENOTFOUND so you don't need to handle that case specially). The + * stream should be opened for writing in binary mode. + * + * close_func is a function to call to close the stream (most commonly + * fclose, or pclose if the stream was opened using popen()) or NULL if + * the caller wants to take care of closing the stream. + * + * title is the title + * + * cs is a string describing the coordinate system, suitable for passing to + * PROJ (or NULL to not specify a coordinate system). For a coordinate system + * with an EPSG, "EPSG:" followed by the code number is the recommended way + * to specify this. + * + * flags contains a bitwise-or of any file-wide flags - currently only one + * is available: img_FFLAG_EXTENDED. + * + * Returns pointer to an img struct or NULL for error (check img_error() + * for details). Any close function specified is called on error (unless + * stream is NULL). + */ +img *img_write_stream(FILE *stream, int (*close_func)(FILE*), + const char *title, const char * cs, int flags); + +/* Read an item from a processed survey data file + * + * pimg is a pointer to an img struct returned by img_open() + * + * coordinates are returned in p + * + * flags and label name are returned in fields in pimg + * + * Returns img_XXXX as #define-d above + */ +int img_read_item(img *pimg, img_point *p); + +/* Write a item to a .3d file + * + * pimg is a pointer to an img struct returned by img_open_write() + * + * code is one of the img_XXXX #define-d above + * + * flags is the leg, station, or xsect flags + * (meaningful for img_LINE, img_LABEL, and img_XSECT respectively) + * + * s is the label (only meaningful for img_LABEL) + * + * x, y, z are the coordinates + */ +void img_write_item(img *pimg, int code, int flags, const char *s, + double x, double y, double 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); + +/* rewind a processed survey data file opened for reading + * + * This is useful if you want to read the data in several passes. + * + * pimg is a pointer to an img struct returned by img_open() + * + * Returns: non-zero for success, zero for error (check img_error() for + * details) + */ +int img_rewind(img *pimg); + +/* Close a processed survey data file + * + * pimg is a pointer to an img struct returned by img_open() or + * img_open_write() + * + * Returns: non-zero for success, zero for error (check img_error() for + * details) + */ +int img_close(img *pimg); + +/* Codes returned by img_error */ +typedef enum { + IMG_NONE = 0, IMG_FILENOTFOUND, IMG_OUTOFMEMORY, + IMG_CANTOPENOUT, IMG_BADFORMAT, IMG_DIRECTORY, + IMG_READERROR, IMG_WRITEERROR, IMG_TOONEW +} img_errcode; + +/* Read the error code + * + * If img_open(), img_open_survey() or img_open_write() returns NULL, or + * img_rewind() or img_close() returns 0, or img_read_item() returns img_BAD + * then you can call this function to discover why. + */ +img_errcode img_error(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/img.js b/img.js new file mode 100644 index 0000000..5c97de2 --- /dev/null +++ b/img.js @@ -0,0 +1 @@ +var Module=typeof Module!=="undefined"?Module:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=typeof window==="object";var ENVIRONMENT_IS_WORKER=typeof importScripts==="function";var ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;var toLog=e;err("exiting due to exception: "+toLog)}var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};readAsync=function readAsync(filename,onload,onerror){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);nodeFS["readFile"](filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=function(status,toThrow){if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!=="object"){abort("no native wasm support detected")}function getValue(ptr,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP32[ptr>>2];case"float":return HEAPF32[ptr>>2];case"double":return Number(HEAPF64[ptr>>3]);default:abort("invalid type for getValue: "+type)}return null}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}var UTF8Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heap,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heap[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="img.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["n"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["s"];addOnInit(Module["asm"]["o"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync();return{}}var tempDouble;var tempI64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){getWasmTableEntry(func)()}else{getWasmTableEntry(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto==="object"&&typeof crypto["getRandomValues"]==="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}}}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){FS.forceLoadFile(node);var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(low,high){return low}};function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_open(path,flags,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(path);var mode=varargs?SYSCALLS.get():0;var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function abortOnCannotGrowMemory(requestedSize){abort("OOM")}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;abortOnCannotGrowMemory(requestedSize)}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator==="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAP32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAP32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _tzset_impl(){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAP32[__get_timezone()>>2]=stdTimezoneOffset*60;HEAP32[__get_daylight()>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocateUTF8(winterName);var summerNamePtr=allocateUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAP32[__get_tzname()+4>>2]=summerNamePtr}else{HEAP32[__get_tzname()>>2]=summerNamePtr;HEAP32[__get_tzname()+4>>2]=winterNamePtr}}function _tzset(){if(_tzset.called)return;_tzset.called=true;_tzset_impl()}function _mktime(tmPtr){_tzset();var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();return date.getTime()/1e3|0}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var asmLibraryArg={"c":___syscall_fcntl64,"g":___syscall_ioctl,"h":___syscall_open,"j":_emscripten_memcpy_big,"k":_emscripten_resize_heap,"d":_environ_get,"e":_environ_sizes_get,"b":_fd_close,"f":_fd_read,"i":_fd_seek,"a":_fd_write,"l":_mktime,"m":_tzset};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["o"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["p"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["q"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["r"]).apply(null,arguments)};var _jsImgOpen=Module["_jsImgOpen"]=function(){return(_jsImgOpen=Module["_jsImgOpen"]=Module["asm"]["t"]).apply(null,arguments)};var _jsImgReadItem=Module["_jsImgReadItem"]=function(){return(_jsImgReadItem=Module["_jsImgReadItem"]=Module["asm"]["u"]).apply(null,arguments)};var _getLabel=Module["_getLabel"]=function(){return(_getLabel=Module["_getLabel"]=Module["asm"]["v"]).apply(null,arguments)};var _getFlags=Module["_getFlags"]=function(){return(_getFlags=Module["_getFlags"]=Module["asm"]["w"]).apply(null,arguments)};var _getTitle=Module["_getTitle"]=function(){return(_getTitle=Module["_getTitle"]=Module["asm"]["x"]).apply(null,arguments)};var _getCs=Module["_getCs"]=function(){return(_getCs=Module["_getCs"]=Module["asm"]["y"]).apply(null,arguments)};var _getDatestamp=Module["_getDatestamp"]=function(){return(_getDatestamp=Module["_getDatestamp"]=Module["asm"]["z"]).apply(null,arguments)};var _getL=Module["_getL"]=function(){return(_getL=Module["_getL"]=Module["asm"]["A"]).apply(null,arguments)};var _getR=Module["_getR"]=function(){return(_getR=Module["_getR"]=Module["asm"]["B"]).apply(null,arguments)};var _getU=Module["_getU"]=function(){return(_getU=Module["_getU"]=Module["asm"]["C"]).apply(null,arguments)};var _getD=Module["_getD"]=function(){return(_getD=Module["_getD"]=Module["asm"]["D"]).apply(null,arguments)};var _getMvx=Module["_getMvx"]=function(){return(_getMvx=Module["_getMvx"]=Module["asm"]["E"]).apply(null,arguments)};var _getMvy=Module["_getMvy"]=function(){return(_getMvy=Module["_getMvy"]=Module["asm"]["F"]).apply(null,arguments)};var _getMvz=Module["_getMvz"]=function(){return(_getMvz=Module["_getMvz"]=Module["asm"]["G"]).apply(null,arguments)};var __get_tzname=Module["__get_tzname"]=function(){return(__get_tzname=Module["__get_tzname"]=Module["asm"]["H"]).apply(null,arguments)};var __get_daylight=Module["__get_daylight"]=function(){return(__get_daylight=Module["__get_daylight"]=Module["asm"]["I"]).apply(null,arguments)};var __get_timezone=Module["__get_timezone"]=function(){return(__get_timezone=Module["__get_timezone"]=Module["asm"]["J"]).apply(null,arguments)};Module["getValue"]=getValue;Module["UTF8ToString"]=UTF8ToString;Module["FS"]=FS;var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); diff --git a/img.wasm b/img.wasm new file mode 100755 index 0000000000000000000000000000000000000000..8a2a6fd19ebb62706ebadee4e26e30786a6f815d GIT binary patch literal 59773 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 literal 0 HcmV?d00001 diff --git a/img_shim.c b/img_shim.c new file mode 100644 index 0000000..71e993c --- /dev/null +++ b/img_shim.c @@ -0,0 +1,62 @@ +#include + +#include "img.h" + +void EMSCRIPTEN_KEEPALIVE jsImgOpen(img **ppStruct, char *filename) { + img *pStruct = img_open(filename); + *ppStruct = pStruct; +} + +void EMSCRIPTEN_KEEPALIVE jsImgReadItem(img **ppStruct, img *pStruct, int *code) { + *code = img_read_item(pStruct, &pStruct->mv); + *ppStruct = pStruct; +} + +/* getters */ +void EMSCRIPTEN_KEEPALIVE getLabel(img *pStruct, char **label) { + *label = pStruct->label; +} + +void EMSCRIPTEN_KEEPALIVE getFlags(img *pStruct, int *flags) { + *flags = pStruct->flags; +} + +void EMSCRIPTEN_KEEPALIVE getTitle(img *pStruct, char **title) { + *title = pStruct->title; +} + +void EMSCRIPTEN_KEEPALIVE getCs(img *pStruct, char **cs) { + *cs = pStruct->cs; +} + +void EMSCRIPTEN_KEEPALIVE getDatestamp(img *pStruct, char **datestamp) { + *datestamp = pStruct->datestamp; +} + +void EMSCRIPTEN_KEEPALIVE getL(img *pStruct, double *l) { + *l = pStruct->l; +} + +void EMSCRIPTEN_KEEPALIVE getR(img *pStruct, double *r) { + *r = pStruct->r; +} + +void EMSCRIPTEN_KEEPALIVE getU(img *pStruct, double *u) { + *u = pStruct->u; +} + +void EMSCRIPTEN_KEEPALIVE getD(img *pStruct, double *d) { + *d = pStruct->d; +} + +void EMSCRIPTEN_KEEPALIVE getMvx(img *pStruct, double *mvx) { + *mvx = pStruct->mv.x; +} + +void EMSCRIPTEN_KEEPALIVE getMvy(img *pStruct, double *mvy) { + *mvy = pStruct->mv.y; +} + +void EMSCRIPTEN_KEEPALIVE getMvz(img *pStruct, double *mvz) { + *mvz = pStruct->mv.z; +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..3ba47cf --- /dev/null +++ b/index.js @@ -0,0 +1,214 @@ +const Module = require('./img.js'); +const fetch = require('isomorphic-unfetch'); + +function buildImg(pStruct, code) { + const img = { + code, + ptr: pStruct, + label: (() => { + const labelPtr = Module._malloc(4); + Module._getLabel(pStruct, labelPtr); + const labelVal = Module.getValue(labelPtr, 'i32'); + Module._free(labelPtr); + return Module.UTF8ToString(labelVal); + })(), + flags: (() => { + const flagsPtr = Module._malloc(4); + Module._getFlags(pStruct, flagsPtr); + const flags = Module.getValue(flagsPtr, 'i32'); + Module._free(flagsPtr); + return flags; + })(), + title: (() => { + const titlePtr = Module._malloc(4); + Module._getTitle(pStruct, titlePtr); + const titleVal = Module.getValue(titlePtr, 'i32'); + Module._free(titlePtr); + return Module.UTF8ToString(titleVal); + })(), + cs: (() => { + const csPtr = Module._malloc(4); + Module._getCs(pStruct, csPtr); + const csVal = Module.getValue(csPtr, 'i32'); + Module._free(csPtr); + return Module.UTF8ToString(csVal); + })(), + datestamp: (() => { + const datestampPtr = Module._malloc(4); + Module._getDatestamp(pStruct, datestampPtr); + const datestampVal = Module.getValue(datestampPtr, 'i32'); + Module._free(datestampPtr); + return Module.UTF8ToString(datestampVal); + })(), + l: (() => { + const lPtr = Module._malloc(4); + Module._getL(pStruct, lPtr); + const l = Module.getValue(lPtr, 'double'); + Module._free(lPtr); + return l; + })(), + r: (() => { + const rPtr = Module._malloc(4); + Module._getR(pStruct, rPtr); + const r = Module.getValue(rPtr, 'double'); + Module._free(rPtr); + return r; + })(), + u: (() => { + const uPtr = Module._malloc(4); + Module._getU(pStruct, uPtr); + const u = Module.getValue(uPtr, 'double'); + Module._free(uPtr); + return u; + })(), + d: (() => { + const dPtr = Module._malloc(4); + Module._getD(pStruct, dPtr); + const d = Module.getValue(dPtr, 'double'); + Module._free(dPtr); + return d; + })(), + mv: { + x: (() => { + const mvxPtr = Module._malloc(4); + Module._getMvx(pStruct, mvxPtr); + const mvx = Module.getValue(mvxPtr, 'double'); + Module._free(mvxPtr); + return mvx; + })(), + y: (() => { + const mvyPtr = Module._malloc(4); + Module._getMvy(pStruct, mvyPtr); + const mvy = Module.getValue(mvyPtr, 'double'); + Module._free(mvyPtr); + return mvy; + })(), + z: (() => { + const mvzPtr = Module._malloc(4); + Module._getMvz(pStruct, mvzPtr); + const mvz = Module.getValue(mvzPtr, 'double'); + Module._free(mvzPtr); + return mvz; + })(), + } + }; + + return img; +} + + +exports.load = function(callback) { + new Promise((resolve) => { + Module['onRuntimeInitialized'] = function() { + Survex = { + imgOpen: (url) => { + const filename = url.split('/').slice(-1)[0]; + const path = `/tmp/${filename}`; + const pathRaw = new TextEncoder().encode(path); + const pathPtr = Module._malloc(pathRaw.length); + const pathChunk = Module.HEAPU8.subarray(pathPtr, pathPtr + pathRaw.length); + pathChunk.set(pathRaw); + + return fetch(url) + .then(r => r.arrayBuffer()) + .then(b => { + const data = new Uint8Array(b); + Module.FS.writeFile(path, data); + }) + .then(() => { + const ppStruct = Module._malloc(4); + Module._jsImgOpen(ppStruct, pathChunk.byteOffset); + Module._free(pathPtr); + const pStruct = Module.getValue(ppStruct, 'i32'); + Module._free(ppStruct); + + return buildImg(pStruct, 0); + }); + }, + imgReadItem: (pStruct) => { + const ppStruct = Module._malloc(4); + const codePtr = Module._malloc(4); + Module._jsImgReadItem(ppStruct, pStruct, codePtr); + + pStruct = Module.getValue(ppStruct, 'i32'); + const code = Module.getValue(codePtr, 'i32'); + Module._free(ppStruct); + Module._free(codePtr); + + return buildImg(pStruct, code); + }, + } + + callback(Survex); + } + }); +}; + +exports.codes = { + 'type': { + '-2': 'img_BAD', + '-1': 'img_STOP', + '0': 'img_MOVE', + '1': 'img_LINE', + '3': 'img_LABEL', + '4': 'img_XSECT', + '5': 'img_XSECT_END', + '6': 'img_ERROR_INFO', + }, + 'leg': { + '1': 'img_FLAG_SURFACE', + '2': 'img_FLAG_DUPLICATE', + '4': 'img_FLAG_SPLAY', + }, + 'station': { + '1': 'img_SFLAG_SURFACE', + '2': 'img_SFLAG_UNDERGROUND', + '4': 'img_SFLAG_ENTRANCE', + '8': 'img_SFLAG_EXPORTED', + '16': 'img_SFLAG_FIXED', + '32': 'img_SFLAG_ANON', + '64': 'img_SFLAG_WALL', + }, + 'file': { + '128': 'img_FFLAG_EXTENDED', + }, + 'xsect': { + '1': 'img_XFLAG_END', + }, + 'style': { + '-1': 'img_STYLE_UNKNOWN', + '0': 'img_STYLE_NORMAL', + '1': 'img_STYLE_DIVING', + '2': 'img_STYLE_CARTESIAN', + '3': 'img_STYLE_CYLPOLAR', + '4': 'img_STYLE_NOSURVEY', + }, + 'raw': { + 'img_MOVE': 0, + 'img_LINE': 1, + 'img_LABEL': 3, + 'img_XSECT': 4, + 'img_XSECT_END': 5, + 'img_ERROR_INFO': 6, + 'img_BAD': -2, + 'img_STOP': -1, + 'img_FLAG_SURFACE': 1, + 'img_FLAG_DUPLICATE': 2, + 'img_FLAG_SPLAY': 4, + 'img_SFLAG_SURFACE': 1, + 'img_SFLAG_UNDERGROUND': 2, + 'img_SFLAG_ENTRANCE': 4, + 'img_SFLAG_EXPORTED': 8, + 'img_SFLAG_FIXED': 16, + 'img_SFLAG_ANON': 32, + 'img_SFLAG_WALL': 64, + 'img_FFLAG_EXTENDED': 128, + 'img_XFLAG_END': 1, + 'img_STYLE_NORMAL': 0, + 'img_STYLE_DIVING': 1, + 'img_STYLE_CARTESIAN': 2, + 'img_STYLE_CYLPOLAR': 3, + 'img_STYLE_NOSURVEY': 4, + 'img_STYLE_UNKNOWN': -1, + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..20d5783 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "survex.js", + "version": "1.0.0", + "description": "Survex JS compatibility layer", + "main": "index.js", + "author": "Paul Walko ", + "license": "MIT", + "dependencies": { + "isomorphic-unfetch": "^3.1.0" + } +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..9a5e00c --- /dev/null +++ b/yarn.lock @@ -0,0 +1,41 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +isomorphic-unfetch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + +node-fetch@^2.6.1: + version "2.6.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" + integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + dependencies: + whatwg-url "^5.0.0" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0"