source: git/src/export.cc @ f96897b

walls-datawalls-data-hanging-as-warning
Last change on this file since f96897b was 4c83f84, checked in by Olly Betts <olly@…>, 12 days ago

Don't check HAVE_CONFIG_H in most cases

This check is only useful for img.c, which is intended to be usable
outside of Survex (and had fallbacks for functions which may not be
available which will get used if built in a non-autotools project).
For all the other source files it's just useless boilerplate.

  • Property mode set to 100644
File size: 48.9 KB
Line 
1/* export.cc
2 * Export to CAD-like formats (DXF, Skencil, SVG, EPS) and also Compass PLT.
3 */
4
5/* Copyright (C) 1994-2022 Olly Betts
6 * Copyright (C) 2004 John Pybus (SVG Output code)
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 */
22
23#include <config.h>
24
25#include "export.h"
26
27#include "wx.h"
28#include <wx/utils.h>
29#include "export3d.h"
30#include "exportfilter.h"
31#include "gpx.h"
32#include "hpgl.h"
33#include "json.h"
34#include "kml.h"
35#include "mainfrm.h"
36#include "pos.h"
37
38#include <float.h>
39#include <locale.h>
40#include <math.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <time.h>
45
46#if defined(HAVE_GETPWUID) && !defined(__DJGPP__)
47# include <pwd.h>
48# include <sys/types.h>
49# include <unistd.h>
50#endif
51
52#include <utility>
53#include <vector>
54
55#include "cmdline.h"
56#include "debug.h"
57#include "filename.h"
58#include "hash.h"
59#include "img_hosted.h"
60#include "message.h"
61#include "useful.h"
62
63#define POINTS_PER_INCH 72.0
64#define POINTS_PER_MM (POINTS_PER_INCH / MM_PER_INCH)
65
66#define SQRT_2          1.41421356237309504880168872420969
67
68// Order here needs to match order of export_format enum in export.h.
69
70const format_info export_format_info[] = {
71    { ".3d", /*Survex 3d files*/207,
72      LABELS|LEGS|SURF|SPLAYS|ENTS|FIXES|EXPORTS, /* FIXME: expand... */
73      LABELS|LEGS|SURF|SPLAYS|ENTS|FIXES|EXPORTS },
74    { ".csv", /*CSV files*/101,
75      LABELS|ENTS|FIXES|EXPORTS,
76      LABELS },
77    { ".dxf", /*DXF files*/411,
78      LABELS|LEGS|SURF|SPLAYS|STNS|PASG|XSECT|WALLS|MARKER_SIZE|TEXT_HEIGHT|GRID|FULL_COORDS|ORIENTABLE,
79      LABELS|LEGS|STNS },
80    { ".eps", /*EPS files*/412,
81      LABELS|LEGS|SURF|SPLAYS|STNS|PASG|XSECT|WALLS|ORIENTABLE,
82      LABELS|LEGS|STNS },
83    { ".gpx", /*GPX files*/413,
84      LABELS|LEGS|SURF|SPLAYS|ENTS|FIXES|EXPORTS|PROJ,
85      LABELS },
86    /* TRANSLATORS: Here "plotter" refers to a machine which draws a printout
87     * on a (usually large) sheet of paper using a pen mounted in a motorised
88     * mechanism. */
89    { ".hpgl", /*HPGL for plotters*/414,
90      LABELS|LEGS|SURF|SPLAYS|STNS|CENTRED|ORIENTABLE,
91      LABELS|LEGS|STNS },
92    { ".json", /*JSON files*/445,
93      LEGS|SURF|SPLAYS|CENTRED,
94      LEGS },
95    { ".kml", /*KML files*/444,
96      LABELS|LEGS|SURF|SPLAYS|PASG|XSECT|WALLS|ENTS|FIXES|EXPORTS|PROJ|CLAMP_TO_GROUND,
97      LABELS|LEGS },
98    /* TRANSLATORS: "Compass" and "Carto" are the names of software packages,
99     * so should not be translated:
100     * http://www.fountainware.com/compass/
101     * http://www.psc-cavers.org/carto/ */
102    { ".plt", /*Compass PLT for use with Carto*/415,
103      LABELS|LEGS|SURF|SPLAYS|ORIENTABLE,
104      LABELS|LEGS },
105    /* TRANSLATORS: "Skencil" is the name of a software package, so should not be
106     * translated: http://www.skencil.org/ */
107    { ".sk", /*Skencil files*/416,
108      LABELS|LEGS|SURF|SPLAYS|STNS|MARKER_SIZE|GRID|SCALE|ORIENTABLE,
109      LABELS|LEGS|STNS },
110    /* TRANSLATORS: Survex is the name of the software, and "pos" refers to a
111     * file extension, so neither should be translated. */
112    { ".pos", /*Survex pos files*/166,
113      LABELS|ENTS|FIXES|EXPORTS,
114      LABELS },
115    { ".svg", /*SVG files*/417,
116      LABELS|LEGS|SURF|SPLAYS|STNS|PASG|XSECT|WALLS|MARKER_SIZE|TEXT_HEIGHT|SCALE|ORIENTABLE,
117      LABELS|LEGS|STNS },
118};
119
120static_assert(sizeof(export_format_info) == FMT_MAX_PLUS_ONE_ * sizeof(export_format_info[0]),
121              "export_format_info[] matches enum export_format");
122
123static void
124html_escape(FILE *fh, const char *s)
125{
126    while (*s) {
127        switch (*s) {
128            case '<':
129                fputs("&lt;", fh);
130                break;
131            case '>':
132                fputs("&gt;", fh);
133                break;
134            case '&':
135                fputs("&amp;", fh);
136                break;
137            default:
138                PUTC(*s, fh);
139        }
140        ++s;
141    }
142}
143
144// Used by Skencil and SVG.
145static const char *layer_name(int mask) {
146    switch (mask) {
147        case LEGS: case LEGS|SURF:
148            return "Legs";
149        case SURF:
150            return "Surface";
151        case STNS:
152            return "Stations";
153        case LABELS:
154            return "Labels";
155        case XSECT:
156            return "Cross-sections";
157        case WALL1: case WALL2: case WALLS:
158            return "Walls";
159        case PASG:
160            return "Passages";
161    }
162    return "";
163}
164
165static double marker_size; /* for station markers */
166static double grid; /* grid spacing (or 0 for no grid) */
167
168const int *
169ExportFilter::passes() const
170{
171    static const int default_passes[] = { LEGS|SURF|STNS|LABELS, 0 };
172    return default_passes;
173}
174
175class DXF : public ExportFilter {
176    const char * to_close;
177    /* for station labels */
178    double text_height;
179    char pending[1024];
180
181  public:
182    explicit DXF(double text_height_)
183        : to_close(0), text_height(text_height_) { pending[0] = '\0'; }
184    const int * passes() const;
185    bool fopen(const wxString& fnm_out);
186    void header(const char *, const char *, time_t,
187                double min_x, double min_y, double min_z,
188                double max_x, double max_y, double max_z);
189    void line(const img_point *, const img_point *, unsigned, bool);
190    void label(const img_point *, const wxString&, bool, int);
191    void cross(const img_point *, const wxString&, bool);
192    void xsect(const img_point *, double, double, double);
193    void wall(const img_point *, double, double);
194    void passage(const img_point *, double, double, double);
195    void tube_end();
196    void footer();
197};
198
199const int *
200DXF::passes() const
201{
202    static const int dxf_passes[] = {
203        PASG, XSECT, WALL1, WALL2, LEGS|SURF|STNS|LABELS, 0
204    };
205    return dxf_passes;
206}
207
208bool
209DXF::fopen(const wxString& fnm_out)
210{
211    // DXF gets written as text rather than binary.
212    fh = wxFopen(fnm_out.fn_str(), wxT("w"));
213    return (fh != NULL);
214}
215
216void
217DXF::header(const char *, const char *, time_t,
218            double min_x, double min_y, double min_z,
219            double max_x, double max_y, double max_z)
220{
221   fprintf(fh, "0\nSECTION\n"
222               "2\nHEADER\n");
223   fprintf(fh, "9\n$EXTMIN\n"); /* lower left corner of drawing */
224   fprintf(fh, "10\n%#-.2f\n", min_x); /* x */
225   fprintf(fh, "20\n%#-.2f\n", min_y); /* y */
226   fprintf(fh, "30\n%#-.2f\n", min_z); /* min z */
227   fprintf(fh, "9\n$EXTMAX\n"); /* upper right corner of drawing */
228   fprintf(fh, "10\n%#-.2f\n", max_x); /* x */
229   fprintf(fh, "20\n%#-.2f\n", max_y); /* y */
230   fprintf(fh, "30\n%#-.2f\n", max_z); /* max z */
231   fprintf(fh, "9\n$PDMODE\n70\n3\n"); /* marker style as CROSS */
232   fprintf(fh, "9\n$PDSIZE\n40\n%6.2f\n", marker_size); /* marker size */
233   fprintf(fh, "0\nENDSEC\n");
234
235   fprintf(fh, "0\nSECTION\n"
236               "2\nTABLES\n");
237   fprintf(fh, "0\nTABLE\n" /* Define CONTINUOUS and DASHED line types. */
238               "2\nLTYPE\n"
239               "70\n10\n"
240               "0\nLTYPE\n"
241               "2\nCONTINUOUS\n"
242               "70\n64\n"
243               "3\nContinuous\n"
244               "72\n65\n"
245               "73\n0\n"
246               "40\n0.0\n"
247               "0\nLTYPE\n"
248               "2\nDASHED\n"
249               "70\n64\n"
250               "3\nDashed\n"
251               "72\n65\n"
252               "73\n2\n"
253               "40\n2.5\n"
254               "49\n1.25\n"
255               "49\n-1.25\n"
256               "0\nLTYPE\n" /* define DOT line type */
257               "2\nDOT\n"
258               "70\n64\n"
259               "3\nDotted\n"
260               "72\n65\n"
261               "73\n2\n"
262               "40\n1\n"
263               "49\n0\n"
264               "49\n1\n"
265               "0\nENDTAB\n");
266   fprintf(fh, "0\nTABLE\n"
267               "2\nLAYER\n");
268   fprintf(fh, "70\n10\n"); /* max # off layers in this DXF file : 10 */
269   /* First Layer: CentreLine */
270   fprintf(fh, "0\nLAYER\n2\nCentreLine\n");
271   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
272   fprintf(fh, "62\n5\n"); /* color: kept the same used by SpeleoGen */
273   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
274   /* Next Layer: Stations */
275   fprintf(fh, "0\nLAYER\n2\nStations\n");
276   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
277   fprintf(fh, "62\n7\n"); /* color: kept the same used by SpeleoGen */
278   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
279   /* Next Layer: Labels */
280   fprintf(fh, "0\nLAYER\n2\nLabels\n");
281   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
282   fprintf(fh, "62\n7\n"); /* color: kept the same used by SpeleoGen */
283   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
284   /* Next Layer: Surface */
285   fprintf(fh, "0\nLAYER\n2\nSurface\n");
286   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
287   fprintf(fh, "62\n5\n"); /* color */
288   fprintf(fh, "6\nDASHED\n"); /* linetype */
289   /* Next Layer: SurfaceStations */
290   fprintf(fh, "0\nLAYER\n2\nSurfaceStations\n");
291   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
292   fprintf(fh, "62\n7\n"); /* color */
293   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
294   /* Next Layer: SurfaceLabels */
295   fprintf(fh, "0\nLAYER\n2\nSurfaceLabels\n");
296   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
297   fprintf(fh, "62\n7\n"); /* color */
298   fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
299   /* Next Layer: Splays */
300   fprintf(fh, "0\nLAYER\n2\nSplays\n");
301   fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
302   fprintf(fh, "62\n5\n"); /* color */
303   fprintf(fh, "6\nDOT\n"); /* linetype;  */
304   if (grid > 0) {
305      /* Next Layer: Grid */
306      fprintf(fh, "0\nLAYER\n2\nGrid\n");
307      fprintf(fh, "70\n64\n"); /* shows layer is referenced by entities */
308      fprintf(fh, "62\n7\n"); /* color: kept the same used by SpeleoGen */
309      fprintf(fh, "6\nCONTINUOUS\n"); /* linetype */
310   }
311   fprintf(fh, "0\nENDTAB\n"
312               "0\nENDSEC\n");
313
314   fprintf(fh, "0\nSECTION\n"
315               "2\nENTITIES\n");
316
317   if (grid > 0) {
318      double x, y;
319      x = floor(min_x / grid) * grid + grid;
320      y = floor(min_y / grid) * grid + grid;
321      while (x < max_x) {
322         /* horizontal line */
323         fprintf(fh, "0\nLINE\n");
324         fprintf(fh, "8\nGrid\n"); /* Layer */
325         fprintf(fh, "10\n%6.2f\n", x);
326         fprintf(fh, "20\n%6.2f\n", min_y);
327         fprintf(fh, "30\n0\n");
328         fprintf(fh, "11\n%6.2f\n", x);
329         fprintf(fh, "21\n%6.2f\n", max_y);
330         fprintf(fh, "31\n0\n");
331         x += grid;
332      }
333      while (y < max_y) {
334         /* vertical line */
335         fprintf(fh, "0\nLINE\n");
336         fprintf(fh, "8\nGrid\n"); /* Layer */
337         fprintf(fh, "10\n%6.2f\n", min_x);
338         fprintf(fh, "20\n%6.2f\n", y);
339         fprintf(fh, "30\n0\n");
340         fprintf(fh, "11\n%6.2f\n", max_x);
341         fprintf(fh, "21\n%6.2f\n", y);
342         fprintf(fh, "31\n0\n");
343         y += grid;
344      }
345   }
346}
347
348void
349DXF::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
350{
351   bool fSurface = (flags & SURF);
352   bool fSplay = (flags & SPLAYS);
353   (void)fPendingMove; /* unused */
354   fprintf(fh, "0\nLINE\n");
355   if (fSurface) { /* select layer */
356      fprintf(fh, "8\nSurface\n" );
357   } else if (fSplay) {
358      fprintf(fh, "8\nSplays\n");
359   } else {
360      fprintf(fh, "8\nCentreLine\n");
361   }
362   fprintf(fh, "10\n%6.2f\n", p1->x);
363   fprintf(fh, "20\n%6.2f\n", p1->y);
364   fprintf(fh, "30\n%6.2f\n", p1->z);
365   fprintf(fh, "11\n%6.2f\n", p->x);
366   fprintf(fh, "21\n%6.2f\n", p->y);
367   fprintf(fh, "31\n%6.2f\n", p->z);
368}
369
370void
371DXF::label(const img_point *p, const wxString& str, bool fSurface, int)
372{
373   /* write station label to dxf file */
374   const char* s = str.utf8_str();
375   fprintf(fh, "0\nTEXT\n");
376   fprintf(fh, fSurface ? "8\nSurfaceLabels\n" : "8\nLabels\n"); /* Layer */
377   fprintf(fh, "10\n%6.2f\n", p->x);
378   fprintf(fh, "20\n%6.2f\n", p->y);
379   fprintf(fh, "30\n%6.2f\n", p->z);
380   fprintf(fh, "40\n%6.2f\n", text_height);
381   fprintf(fh, "1\n%s\n", s);
382}
383
384void
385DXF::cross(const img_point *p, const wxString&, bool fSurface)
386{
387   /* write station marker to dxf file */
388   fprintf(fh, "0\nPOINT\n");
389   fprintf(fh, fSurface ? "8\nSurfaceStations\n" : "8\nStations\n"); /* Layer */
390   fprintf(fh, "10\n%6.2f\n", p->x);
391   fprintf(fh, "20\n%6.2f\n", p->y);
392   fprintf(fh, "30\n%6.2f\n", p->z);
393}
394
395void
396DXF::xsect(const img_point *p, double angle, double d1, double d2)
397{
398   double s = sin(rad(angle));
399   double c = cos(rad(angle));
400   fprintf(fh, "0\nLINE\n");
401   fprintf(fh, "8\nCross-sections\n"); /* Layer */
402   fprintf(fh, "10\n%6.2f\n", p->x + s * d1);
403   fprintf(fh, "20\n%6.2f\n", p->y + c * d1);
404   fprintf(fh, "30\n%6.2f\n", p->z);
405   fprintf(fh, "11\n%6.2f\n", p->x - s * d2);
406   fprintf(fh, "21\n%6.2f\n", p->y - c * d2);
407   fprintf(fh, "31\n%6.2f\n", p->z);
408}
409
410void
411DXF::wall(const img_point *p, double angle, double d)
412{
413   if (!to_close) {
414       fprintf(fh, "0\nPOLYLINE\n");
415       fprintf(fh, "8\nWalls\n"); /* Layer */
416       fprintf(fh, "70\n0\n"); /* bit 0 == 0 => Open polyline */
417       to_close = "0\nSEQEND\n";
418   }
419   double s = sin(rad(angle));
420   double c = cos(rad(angle));
421   fprintf(fh, "0\nVERTEX\n");
422   fprintf(fh, "8\nWalls\n"); /* Layer */
423   fprintf(fh, "10\n%6.2f\n", p->x + s * d);
424   fprintf(fh, "20\n%6.2f\n", p->y + c * d);
425   fprintf(fh, "30\n%6.2f\n", p->z);
426}
427
428void
429DXF::passage(const img_point *p, double angle, double d1, double d2)
430{
431   fprintf(fh, "0\nSOLID\n");
432   fprintf(fh, "8\nPassages\n"); /* Layer */
433   double s = sin(rad(angle));
434   double c = cos(rad(angle));
435   double x1 = p->x + s * d1;
436   double y1 = p->y + c * d1;
437   double x2 = p->x - s * d2;
438   double y2 = p->y - c * d2;
439   if (*pending) {
440       fputs(pending, fh);
441       fprintf(fh, "12\n%6.2f\n22\n%6.2f\n32\n%6.2f\n"
442                   "13\n%6.2f\n23\n%6.2f\n33\n%6.2f\n",
443                   x1, y1, p->z,
444                   x2, y2, p->z);
445   }
446   snprintf(pending, sizeof(pending),
447            "10\n%6.2f\n20\n%6.2f\n30\n%6.2f\n"
448            "11\n%6.2f\n21\n%6.2f\n31\n%6.2f\n",
449            x1, y1, p->z,
450            x2, y2, p->z);
451}
452
453void
454DXF::tube_end()
455{
456   *pending = '\0';
457   if (to_close) {
458      fputs(to_close, fh);
459      to_close = NULL;
460   }
461}
462
463void
464DXF::footer()
465{
466   fprintf(fh, "000\nENDSEC\n");
467   fprintf(fh, "000\nEOF\n");
468}
469
470class Skencil : public ExportFilter {
471    double factor;
472  public:
473    explicit Skencil(double scale)
474        : factor(POINTS_PER_MM * 1000.0 / scale) { }
475    const int * passes() const;
476    void header(const char *, const char *, time_t,
477                double min_x, double min_y, double min_z,
478                double max_x, double max_y, double max_z);
479    void start_pass(int layer);
480    void line(const img_point *, const img_point *, unsigned, bool);
481    void label(const img_point *, const wxString&, bool, int);
482    void cross(const img_point *, const wxString&, bool);
483    void footer();
484};
485
486const int *
487Skencil::passes() const
488{
489    static const int skencil_passes[] = { LEGS|SURF, STNS, LABELS, 0 };
490    return skencil_passes;
491}
492
493void
494Skencil::header(const char *, const char *, time_t,
495                double min_x, double min_y, double /*min_z*/,
496                double max_x, double max_y, double /*max_z*/)
497{
498   fprintf(fh, "##Sketch 1 2\n"); /* File format version */
499   fprintf(fh, "document()\n");
500   fprintf(fh, "layout((%.3f,%.3f),0)\n",
501           (max_x - min_x) * factor, (max_y - min_y) * factor);
502}
503
504void
505Skencil::start_pass(int layer)
506{
507   fprintf(fh, "layer('%s',1,1,0,0,(0,0,0))\n", layer_name(layer));
508}
509
510void
511Skencil::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
512{
513   (void)flags; /* unused */
514   if (fPendingMove) {
515       fprintf(fh, "b()\n");
516       fprintf(fh, "bs(%.3f,%.3f,%.3f)\n", p1->x * factor, p1->y * factor, 0.0);
517   }
518   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n", p->x * factor, p->y * factor, 0.0);
519}
520
521void
522Skencil::label(const img_point *p, const wxString& str, bool fSurface, int)
523{
524   const char* s = str.utf8_str();
525   (void)fSurface; /* unused */
526   fprintf(fh, "fp((0,0,0))\n");
527   fprintf(fh, "le()\n");
528   fprintf(fh, "Fn('Times-Roman')\n");
529   fprintf(fh, "Fs(5)\n");
530   fprintf(fh, "txt('");
531   while (*s) {
532      int ch = *s++;
533      if (ch == '\'' || ch == '\\') PUTC('\\', fh);
534      PUTC(ch, fh);
535   }
536   fprintf(fh, "',(%.3f,%.3f))\n", p->x * factor, p->y * factor);
537}
538
539void
540Skencil::cross(const img_point *p, const wxString&, bool fSurface)
541{
542   (void)fSurface; /* unused */
543   fprintf(fh, "b()\n");
544   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n",
545           p->x * factor - marker_size, p->y * factor - marker_size, 0.0);
546   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n",
547           p->x * factor + marker_size, p->y * factor + marker_size, 0.0);
548   fprintf(fh, "bn()\n");
549   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n",
550           p->x * factor + marker_size, p->y * factor - marker_size, 0.0);
551   fprintf(fh, "bs(%.3f,%.3f,%.3f)\n",
552           p->x * factor - marker_size, p->y * factor + marker_size, 0.0);
553}
554
555void
556Skencil::footer(void)
557{
558   fprintf(fh, "guidelayer('Guide Lines',1,0,0,1,(0,0,1))\n");
559   if (grid) {
560      fprintf(fh, "grid((0,0,%.3f,%.3f),1,(0,0,1),'Grid')\n",
561              grid * factor, grid * factor);
562   }
563}
564
565typedef struct point {
566   img_point p;
567   const char *label;
568   struct point *next;
569} point;
570
571#define HTAB_SIZE 0x2000
572
573static point **htab;
574
575static void
576set_name(const img_point *p, const char *s)
577{
578   int hash;
579   point *pt;
580   union {
581      char data[sizeof(int) * 3];
582      int x[3];
583   } u;
584
585   u.x[0] = (int)(p->x * 100);
586   u.x[1] = (int)(p->y * 100);
587   u.x[2] = (int)(p->z * 100);
588   hash = (hash_data(u.data, sizeof(int) * 3) & (HTAB_SIZE - 1));
589   for (pt = htab[hash]; pt; pt = pt->next) {
590      if (pt->p.x == p->x && pt->p.y == p->y && pt->p.z == p->z) {
591         /* already got name for these coordinates */
592         /* FIXME: what about multiple names for the same station? */
593         return;
594      }
595   }
596
597   pt = osnew(point);
598   pt->label = osstrdup(s);
599   pt->p = *p;
600   pt->next = htab[hash];
601   htab[hash] = pt;
602
603   return;
604}
605
606static const char *
607find_name(const img_point *p)
608{
609   int hash;
610   point *pt;
611   union {
612      char data[sizeof(int) * 3];
613      int x[3];
614   } u;
615   wxASSERT(p);
616
617   u.x[0] = (int)(p->x * 100);
618   u.x[1] = (int)(p->y * 100);
619   u.x[2] = (int)(p->z * 100);
620   hash = (hash_data(u.data, sizeof(int) * 3) & (HTAB_SIZE - 1));
621   for (pt = htab[hash]; pt; pt = pt->next) {
622      if (pt->p.x == p->x && pt->p.y == p->y && pt->p.z == p->z)
623         return pt->label;
624   }
625   return "?";
626}
627
628class SVG : public ExportFilter {
629    const char * to_close;
630    bool close_g;
631    double factor;
632    /* for station labels */
633    double text_height;
634    char pending[1024];
635
636  public:
637    SVG(double scale, double text_height_)
638        : to_close(NULL),
639          close_g(false),
640          factor(1000.0 / scale),
641          text_height(text_height_) {
642        pending[0] = '\0';
643    }
644    const int * passes() const;
645    void header(const char *, const char *, time_t,
646                double min_x, double min_y, double min_z,
647                double max_x, double max_y, double max_z);
648    void start_pass(int layer);
649    void line(const img_point *, const img_point *, unsigned, bool);
650    void label(const img_point *, const wxString&, bool, int);
651    void cross(const img_point *, const wxString&, bool);
652    void xsect(const img_point *, double, double, double);
653    void wall(const img_point *, double, double);
654    void passage(const img_point *, double, double, double);
655    void tube_end();
656    void footer();
657};
658
659const int *
660SVG::passes() const
661{
662    static const int svg_passes[] = {
663        PASG, LEGS|SURF, XSECT, WALL1, WALL2, LABELS, STNS, 0
664    };
665    return svg_passes;
666}
667
668void
669SVG::header(const char * title, const char *, time_t,
670            double min_x, double min_y, double /*min_z*/,
671            double max_x, double max_y, double /*max_z*/)
672{
673   const char *unit = "mm";
674   const double SVG_MARGIN = 5.0; // In units of "unit".
675   htab = (point **)osmalloc(HTAB_SIZE * ossizeof(point *));
676   for (size_t i = 0; i < HTAB_SIZE; ++i) htab[i] = NULL;
677   fprintf(fh, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
678   double width = (max_x - min_x) * factor + SVG_MARGIN * 2;
679   double height = (max_y - min_y) * factor + SVG_MARGIN * 2;
680   fprintf(fh, "<svg version=\"1.1\" baseProfile=\"full\"\n"
681               "xmlns=\"http://www.w3.org/2000/svg\"\n"
682               "xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
683               "xmlns:ev=\"http://www.w3.org/2001/xml-events\"\n"
684               "width=\"%.3f%s\" height=\"%.3f%s\"\n"
685               "viewBox=\"0 0 %0.3f %0.3f\">\n",
686           width, unit, height, unit, width, height);
687   if (title && title[0]) {
688       fputs("<title>", fh);
689       html_escape(fh, title);
690       fputs("</title>\n", fh);
691   }
692   fprintf(fh, "<g transform=\"translate(%.3f %.3f)\">\n",
693           SVG_MARGIN - min_x * factor, SVG_MARGIN + max_y * factor);
694   to_close = NULL;
695   close_g = false;
696}
697
698void
699SVG::start_pass(int layer)
700{
701   if (to_close) {
702      fputs(to_close, fh);
703      to_close = NULL;
704   }
705   if (close_g) {
706      fprintf(fh, "</g>\n");
707   }
708   fprintf(fh, "<g id=\"%s\"", layer_name(layer));
709   if (layer & LEGS)
710      fprintf(fh, " stroke=\"black\" fill=\"none\" stroke-width=\"0.4px\"");
711   else if (layer & STNS)
712      fprintf(fh, " stroke=\"black\" fill=\"none\" stroke-width=\"0.05px\"");
713   else if (layer & LABELS)
714      fprintf(fh, " font-size=\"%.3fem\"", text_height);
715   else if (layer & XSECT)
716      fprintf(fh, " stroke=\"grey\" fill=\"none\" stroke-width=\"0.1px\"");
717   else if (layer & WALLS)
718      fprintf(fh, " stroke=\"black\" fill=\"none\" stroke-width=\"0.1px\"");
719   else if (layer & PASG)
720      fprintf(fh, " stroke=\"none\" fill=\"peru\"");
721   fprintf(fh, ">\n");
722
723   close_g = true;
724}
725
726void
727SVG::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
728{
729   bool splay = (flags & SPLAYS);
730   if (fPendingMove) {
731       if (to_close) {
732           fputs(to_close, fh);
733       }
734       fprintf(fh, "<path ");
735       if (splay) fprintf(fh, "stroke=\"grey\" stroke-width=\"0.1px\" ");
736       fprintf(fh, "d=\"M%.3f %.3f", p1->x * factor, p1->y * -factor);
737   }
738   fprintf(fh, "L%.3f %.3f", p->x * factor, p->y * -factor);
739   to_close = "\"/>\n";
740}
741
742void
743SVG::label(const img_point *p, const wxString& str, bool fSurface, int)
744{
745   const char* s = str.utf8_str();
746   (void)fSurface; /* unused */
747   fprintf(fh, "<text transform=\"translate(%.3f %.3f)\">",
748           p->x * factor, p->y * -factor);
749   html_escape(fh, s);
750   fputs("</text>\n", fh);
751   set_name(p, s);
752}
753
754void
755SVG::cross(const img_point *p, const wxString& str, bool fSurface)
756{
757   const char* s = str.utf8_str();
758   (void)fSurface; /* unused */
759   fprintf(fh, "<circle id=\"%s\" cx=\"%.3f\" cy=\"%.3f\" r=\"%.3f\"/>\n",
760           s, p->x * factor, p->y * -factor, marker_size * SQRT_2);
761   fprintf(fh, "<path d=\"M%.3f %.3fL%.3f %.3fM%.3f %.3fL%.3f %.3f\"/>\n",
762           p->x * factor - marker_size, p->y * -factor - marker_size,
763           p->x * factor + marker_size, p->y * -factor + marker_size,
764           p->x * factor + marker_size, p->y * -factor - marker_size,
765           p->x * factor - marker_size, p->y * -factor + marker_size);
766}
767
768void
769SVG::xsect(const img_point *p, double angle, double d1, double d2)
770{
771   double s = sin(rad(angle));
772   double c = cos(rad(angle));
773   fprintf(fh, "<path d=\"M%.3f %.3fL%.3f %.3f\"/>\n",
774           (p->x + s * d1) * factor, (p->y + c * d1) * -factor,
775           (p->x - s * d2) * factor, (p->y - c * d2) * -factor);
776}
777
778void
779SVG::wall(const img_point *p, double angle, double d)
780{
781   if (!to_close) {
782       fprintf(fh, "<path d=\"M");
783       to_close = "\"/>\n";
784   } else {
785       fprintf(fh, "L");
786   }
787   double s = sin(rad(angle));
788   double c = cos(rad(angle));
789   fprintf(fh, "%.3f %.3f", (p->x + s * d) * factor, (p->y + c * d) * -factor);
790}
791
792void
793SVG::passage(const img_point *p, double angle, double d1, double d2)
794{
795   double s = sin(rad(angle));
796   double c = cos(rad(angle));
797   double x1 = (p->x + s * d1) * factor;
798   double y1 = (p->y + c * d1) * -factor;
799   double x2 = (p->x - s * d2) * factor;
800   double y2 = (p->y - c * d2) * -factor;
801   if (*pending) {
802       fputs(pending, fh);
803       fprintf(fh, "L%.3f %.3fL%.3f %.3fZ\"/>\n", x2, y2, x1, y1);
804   }
805   snprintf(pending, sizeof(pending),
806            "<path d=\"M%.3f %.3fL%.3f %.3f", x1, y1, x2, y2);
807}
808
809void
810SVG::tube_end()
811{
812   *pending = '\0';
813   if (to_close) {
814      fputs(to_close, fh);
815      to_close = NULL;
816   }
817}
818
819void
820SVG::footer()
821{
822   if (to_close) {
823      fputs(to_close, fh);
824      to_close = NULL;
825   }
826   if (close_g) {
827      fprintf(fh, "</g>\n");
828      close_g = false;
829   }
830   fprintf(fh, "</g>\n</svg>\n");
831}
832
833class PLT : public ExportFilter {
834    string escaped;
835
836    const char * find_name_plt(const img_point *p);
837
838    double min_N, max_N, min_E, max_E, min_A, max_A;
839
840  public:
841    PLT() { }
842    const int * passes() const;
843    void header(const char *, const char *, time_t,
844                double min_x, double min_y, double min_z,
845                double max_x, double max_y, double max_z);
846    void line(const img_point *, const img_point *, unsigned, bool);
847    void label(const img_point *, const wxString&, bool, int);
848    void footer();
849};
850
851const int *
852PLT::passes() const
853{
854    static const int plt_passes[] = { LABELS, LEGS|SURF, 0 };
855    return plt_passes;
856}
857
858void
859PLT::header(const char *title, const char *, time_t,
860            double min_x, double min_y, double min_z,
861            double max_x, double max_y, double max_z)
862{
863   // FIXME: allow survey to be set from aven somehow!
864   const char *survey = NULL;
865   htab = (point **)osmalloc(HTAB_SIZE * ossizeof(point *));
866   for (size_t i = 0; i < HTAB_SIZE; ++i) htab[i] = NULL;
867   /* Survex is E, N, Alt - PLT file is N, E, Alt */
868   min_N = min_y / METRES_PER_FOOT;
869   max_N = max_y / METRES_PER_FOOT;
870   min_E = min_x / METRES_PER_FOOT;
871   max_E = max_x / METRES_PER_FOOT;
872   min_A = min_z / METRES_PER_FOOT;
873   max_A = max_z / METRES_PER_FOOT;
874   fprintf(fh, "Z %.3f %.3f %.3f %.3f %.3f %.3f\r\n",
875           min_N, max_N, min_E, max_E, min_A, max_A);
876   fprintf(fh, "N%s D 1 1 1 C%s\r\n", survey ? survey : "X",
877           (title && title[0]) ? title : "X");
878}
879
880void
881PLT::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
882{
883   if (fPendingMove) {
884       /* Survex is E, N, Alt - PLT file is N, E, Alt */
885       fprintf(fh, "M %.3f %.3f %.3f ",
886               p1->y / METRES_PER_FOOT, p1->x / METRES_PER_FOOT, p1->z / METRES_PER_FOOT);
887       /* dummy passage dimensions are required to avoid compass bug */
888       fprintf(fh, "S%s P -9 -9 -9 -9\r\n", find_name_plt(p1));
889   }
890   /* Survex is E, N, Alt - PLT file is N, E, Alt */
891   fprintf(fh, "D %.3f %.3f %.3f ",
892           p->y / METRES_PER_FOOT, p->x / METRES_PER_FOOT, p->z / METRES_PER_FOOT);
893   /* dummy passage dimensions are required to avoid compass bug */
894   fprintf(fh, "S%s P -9 -9 -9 -9", find_name_plt(p));
895   if (flags & (SURF|SPLAYS)) {
896       fprintf(fh, " #|");
897       if (flags & SURF) PUTC('P', fh);
898       if (flags & SPLAYS) PUTC('S', fh);
899       PUTC('#', fh);
900   }
901   fprintf(fh, "\r\n");
902}
903
904const char *
905PLT::find_name_plt(const img_point *p)
906{
907    const char * s = find_name(p);
908    escaped.resize(0);
909    if (*s == '\0') {
910        // Anonymous station - generate a name based on the coordinates as
911        // that's at least reproducible.  We start it with "%+" or "%-" since
912        // escape any % in a real station name below, but only insert %
913        // followed by two hex digits.  We encode the coordinates in
914        // centimetres with the sign encoded as - or + followed by the absolute
915        // value in hex.
916        int x = int(p->x * 100.0);
917        int y = int(p->y * 100.0);
918        int z = int(p->z * 100.0);
919        char buf[64];
920        snprintf(buf, sizeof(buf), "%%%c%x%c%x%c%x",
921                 (x < 0 ? '-' : '+'), abs(x),
922                 (y < 0 ? '-' : '+'), abs(y),
923                 (z < 0 ? '-' : '+'), abs(z));
924        escaped = buf;
925        return escaped.c_str();
926    }
927
928    // PLT format can't handle spaces or control characters, so escape them
929    // like in URLs (an arbitrary choice of escaping, but at least a familiar
930    // one and % isn't likely to occur in station names).
931    const char * q;
932    for (q = s; *q; ++q) {
933        unsigned char ch = *q;
934        if (ch <= ' ' || ch == '%') {
935            escaped.append(s, q - s);
936            escaped += '%';
937            escaped += "0123456789abcdef"[ch >> 4];
938            escaped += "0123456789abcdef"[ch & 0x0f];
939            s = q + 1;
940        }
941    }
942    if (!escaped.empty()) {
943        escaped.append(s, q - s);
944        return escaped.c_str();
945    }
946    return s;
947}
948
949void
950PLT::label(const img_point *p, const wxString& str, bool fSurface, int)
951{
952   const char* s = str.utf8_str();
953   (void)fSurface; /* unused */
954   set_name(p, s);
955}
956
957void
958PLT::footer(void)
959{
960   /* Survex is E, N, Alt - PLT file is N, E, Alt */
961   fprintf(fh, "X %.3f %.3f %.3f %.3f %.3f %.3f\r\n",
962           min_N, max_N, min_E, max_E, min_A, max_A);
963   /* Yucky DOS "end of textfile" marker */
964   PUTC('\x1a', fh);
965}
966
967class EPS : public ExportFilter {
968    double factor;
969    bool first;
970    vector<pair<double, double>> psg;
971  public:
972    explicit EPS(double scale)
973        : factor(POINTS_PER_MM * 1000.0 / scale) { }
974    const int * passes() const;
975    void header(const char *, const char *, time_t,
976                double min_x, double min_y, double min_z,
977                double max_x, double max_y, double max_z);
978    void start_pass(int layer);
979    void line(const img_point *, const img_point *, unsigned, bool);
980    void label(const img_point *, const wxString&, bool, int);
981    void cross(const img_point *, const wxString&, bool);
982    void xsect(const img_point *, double, double, double);
983    void wall(const img_point *, double, double);
984    void passage(const img_point *, double, double, double);
985    void tube_end();
986    void footer();
987};
988
989const int *
990EPS::passes() const
991{
992    static const int eps_passes[] = {
993        PASG, XSECT, WALL1, WALL2, LEGS|SURF|STNS|LABELS, 0
994    };
995    return eps_passes;
996}
997
998void
999EPS::header(const char *title, const char *, time_t,
1000            double min_x, double min_y, double /*min_z*/,
1001            double max_x, double max_y, double /*max_z*/)
1002{
1003   const char * fontname_labels = "helvetica"; // FIXME
1004   int fontsize_labels = 10; // FIXME
1005   fputs("%!PS-Adobe-2.0 EPSF-1.2\n", fh);
1006   fputs("%%Creator: Survex " VERSION " EPS Export Filter\n", fh);
1007
1008   if (title && title[0])
1009       fprintf(fh, "%%%%Title: %s\n", title);
1010
1011   char buf[64];
1012   time_t now = time(NULL);
1013   if (strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %Z\n", localtime(&now))) {
1014      fputs("%%CreationDate: ", fh);
1015      fputs(buf, fh);
1016   }
1017
1018   string name;
1019#if defined(HAVE_GETPWUID) && !defined(__DJGPP__)
1020   struct passwd * ent = getpwuid(getuid());
1021   if (ent && ent->pw_gecos[0]) name = ent->pw_gecos;
1022#endif
1023   if (name.empty()) {
1024       name = ::wxGetUserName().mb_str();
1025       if (name.empty()) {
1026           name = ::wxGetUserId().mb_str();
1027       }
1028   }
1029   if (!name.empty()) {
1030       fprintf(fh, "%%%%For: %s\n", name.c_str());
1031   }
1032
1033   fprintf(fh, "%%%%BoundingBox: %d %d %d %d\n",
1034           int(floor(min_x * factor)), int(floor(min_y * factor)),
1035           int(ceil(max_x * factor)), int(ceil(max_y * factor)));
1036   fprintf(fh, "%%%%HiResBoundingBox: %.4f %.4f %.4f %.4f\n",
1037           min_x * factor, min_y * factor, max_x * factor, max_y * factor);
1038   fputs("%%LanguageLevel: 1\n"
1039         "%%PageOrder: Ascend\n"
1040         "%%Pages: 1\n"
1041         "%%Orientation: Portrait\n", fh);
1042
1043   fprintf(fh, "%%%%DocumentFonts: %s\n", fontname_labels);
1044
1045   fputs("%%EndComments\n"
1046         "%%Page 1 1\n"
1047         "save countdictstack mark\n", fh);
1048
1049   /* this code adapted from a2ps */
1050   fputs("%%BeginResource: encoding ISO88591Encoding\n"
1051         "/ISO88591Encoding [\n", fh);
1052   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1053   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1054   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1055   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1056   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1057   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1058   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1059   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1060   fputs(
1061"/space /exclam /quotedbl /numbersign\n"
1062"/dollar /percent /ampersand /quoteright\n"
1063"/parenleft /parenright /asterisk /plus\n"
1064"/comma /minus /period /slash\n"
1065"/zero /one /two /three\n"
1066"/four /five /six /seven\n"
1067"/eight /nine /colon /semicolon\n"
1068"/less /equal /greater /question\n"
1069"/at /A /B /C /D /E /F /G\n"
1070"/H /I /J /K /L /M /N /O\n"
1071"/P /Q /R /S /T /U /V /W\n"
1072"/X /Y /Z /bracketleft\n"
1073"/backslash /bracketright /asciicircum /underscore\n"
1074"/quoteleft /a /b /c /d /e /f /g\n"
1075"/h /i /j /k /l /m /n /o\n"
1076"/p /q /r /s /t /u /v /w\n"
1077"/x /y /z /braceleft\n"
1078"/bar /braceright /asciitilde /.notdef\n", fh);
1079   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1080   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1081   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1082   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1083   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1084   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1085   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1086   fputs("/.notdef /.notdef /.notdef /.notdef\n", fh);
1087   fputs(
1088"/space /exclamdown /cent /sterling\n"
1089"/currency /yen /brokenbar /section\n"
1090"/dieresis /copyright /ordfeminine /guillemotleft\n"
1091"/logicalnot /hyphen /registered /macron\n"
1092"/degree /plusminus /twosuperior /threesuperior\n"
1093"/acute /mu /paragraph /bullet\n"
1094"/cedilla /onesuperior /ordmasculine /guillemotright\n"
1095"/onequarter /onehalf /threequarters /questiondown\n"
1096"/Agrave /Aacute /Acircumflex /Atilde\n"
1097"/Adieresis /Aring /AE /Ccedilla\n"
1098"/Egrave /Eacute /Ecircumflex /Edieresis\n"
1099"/Igrave /Iacute /Icircumflex /Idieresis\n"
1100"/Eth /Ntilde /Ograve /Oacute\n"
1101"/Ocircumflex /Otilde /Odieresis /multiply\n"
1102"/Oslash /Ugrave /Uacute /Ucircumflex\n"
1103"/Udieresis /Yacute /Thorn /germandbls\n"
1104"/agrave /aacute /acircumflex /atilde\n"
1105"/adieresis /aring /ae /ccedilla\n"
1106"/egrave /eacute /ecircumflex /edieresis\n"
1107"/igrave /iacute /icircumflex /idieresis\n"
1108"/eth /ntilde /ograve /oacute\n"
1109"/ocircumflex /otilde /odieresis /divide\n"
1110"/oslash /ugrave /uacute /ucircumflex\n"
1111"/udieresis /yacute /thorn /ydieresis\n"
1112"] def\n"
1113"%%EndResource\n", fh);
1114
1115   /* this code adapted from a2ps */
1116   fputs(
1117"/reencode {\n" /* def */
1118"dup length 5 add dict begin\n"
1119"{\n" /* forall */
1120"1 index /FID ne\n"
1121"{ def }{ pop pop } ifelse\n"
1122"} forall\n"
1123"/Encoding exch def\n"
1124
1125/* Use the font's bounding box to determine the ascent, descent,
1126 * and overall height; don't forget that these values have to be
1127 * transformed using the font's matrix.
1128 * We use `load' because sometimes BBox is executable, sometimes not.
1129 * Since we need 4 numbers and not an array avoid BBox from being executed
1130 */
1131"/FontBBox load aload pop\n"
1132"FontMatrix transform /Ascent exch def pop\n"
1133"FontMatrix transform /Descent exch def pop\n"
1134"/FontHeight Ascent Descent sub def\n"
1135
1136/* Define these in case they're not in the FontInfo (also, here
1137 * they're easier to get to.
1138 */
1139"/UnderlinePosition 1 def\n"
1140"/UnderlineThickness 1 def\n"
1141
1142/* Get the underline position and thickness if they're defined. */
1143"currentdict /FontInfo known {\n"
1144"FontInfo\n"
1145
1146"dup /UnderlinePosition known {\n"
1147"dup /UnderlinePosition get\n"
1148"0 exch FontMatrix transform exch pop\n"
1149"/UnderlinePosition exch def\n"
1150"} if\n"
1151
1152"dup /UnderlineThickness known {\n"
1153"/UnderlineThickness get\n"
1154"0 exch FontMatrix transform exch pop\n"
1155"/UnderlineThickness exch def\n"
1156"} if\n"
1157
1158"} if\n"
1159"currentdict\n"
1160"end\n"
1161"} bind def\n", fh);
1162
1163   fprintf(fh, "/lab ISO88591Encoding /%s findfont reencode definefont pop\n",
1164           fontname_labels);
1165
1166   fprintf(fh, "/lab findfont %d scalefont setfont\n", int(fontsize_labels));
1167
1168#if 0
1169   /* C<digit> changes colour */
1170   /* FIXME: read from ini */
1171   {
1172      size_t i;
1173      for (i = 0; i < sizeof(colour) / sizeof(colour[0]); ++i) {
1174         fprintf(fh, "/C%u {stroke %.3f %.3f %.3f setrgbcolor} def\n", i,
1175                 (double)(colour[i] & 0xff0000) / 0xff0000,
1176                 (double)(colour[i] & 0xff00) / 0xff00,
1177                 (double)(colour[i] & 0xff) / 0xff);
1178      }
1179   }
1180   fputs("C0\n", fh);
1181#endif
1182
1183   /* Postscript definition for drawing a cross */
1184   fprintf(fh, "/X {stroke moveto %.2f %.2f rmoveto %.2f %.2f rlineto "
1185           "%.2f 0 rmoveto %.2f %.2f rlineto %.2f %.2f rmoveto} def\n",
1186           -marker_size, -marker_size,  marker_size * 2, marker_size * 2,
1187           -marker_size * 2,  marker_size * 2, -marker_size * 2,
1188           -marker_size, marker_size );
1189
1190   /* define some functions to keep file short */
1191   fputs("/M {stroke moveto} def\n"
1192         "/P {stroke newpath moveto} def\n"
1193         "/F {closepath gsave 0.8 setgray fill grestore} def\n"
1194         "/L {lineto} def\n"
1195         "/R {rlineto} def\n"
1196         "/S {show} def\n", fh);
1197
1198   fprintf(fh, "gsave %.8f dup scale\n", factor);
1199#if 0
1200   if (grid > 0) {
1201      double x, y;
1202      x = floor(min_x / grid) * grid + grid;
1203      y = floor(min_y / grid) * grid + grid;
1204      while (x < max_x) {
1205         /* horizontal line */
1206         fprintf(fh, "0\nLINE\n");
1207         fprintf(fh, "8\nGrid\n"); /* Layer */
1208         fprintf(fh, "10\n%6.2f\n", x);
1209         fprintf(fh, "20\n%6.2f\n", min_y);
1210         fprintf(fh, "30\n0\n");
1211         fprintf(fh, "11\n%6.2f\n", x);
1212         fprintf(fh, "21\n%6.2f\n", max_y);
1213         fprintf(fh, "31\n0\n");
1214         x += grid;
1215      }
1216      while (y < max_y) {
1217         /* vertical line */
1218         fprintf(fh, "0\nLINE\n");
1219         fprintf(fh, "8\nGrid\n"); /* Layer */
1220         fprintf(fh, "10\n%6.2f\n", min_x);
1221         fprintf(fh, "20\n%6.2f\n", y);
1222         fprintf(fh, "30\n0\n");
1223         fprintf(fh, "11\n%6.2f\n", max_x);
1224         fprintf(fh, "21\n%6.2f\n", y);
1225         fprintf(fh, "31\n0\n");
1226         y += grid;
1227      }
1228   }
1229#endif
1230}
1231
1232void
1233EPS::start_pass(int layer)
1234{
1235    first = true;
1236    switch (layer) {
1237        case LEGS|SURF|STNS|LABELS:
1238            fprintf(fh, "0.1 setlinewidth\n");
1239            break;
1240        case PASG: case XSECT: case WALL1: case WALL2:
1241            fprintf(fh, "0.01 setlinewidth\n");
1242            break;
1243    }
1244}
1245
1246void
1247EPS::line(const img_point *p1, const img_point *p, unsigned flags, bool fPendingMove)
1248{
1249   (void)flags; /* unused */
1250   if (fPendingMove) {
1251       fprintf(fh, "%.2f %.2f M\n", p1->x, p1->y);
1252   }
1253   fprintf(fh, "%.2f %.2f L\n", p->x, p->y);
1254}
1255
1256void
1257EPS::label(const img_point *p, const wxString& str, bool /*fSurface*/, int)
1258{
1259   const char* s = str.utf8_str();
1260   fprintf(fh, "%.2f %.2f M\n", p->x, p->y);
1261   PUTC('(', fh);
1262   while (*s) {
1263       unsigned char ch = *s++;
1264       switch (ch) {
1265           case '(': case ')': case '\\': /* need to escape these characters */
1266               PUTC('\\', fh);
1267               PUTC(ch, fh);
1268               break;
1269           default:
1270               PUTC(ch, fh);
1271               break;
1272       }
1273   }
1274   fputs(") S\n", fh);
1275}
1276
1277void
1278EPS::cross(const img_point *p, const wxString&, bool fSurface)
1279{
1280   (void)fSurface; /* unused */
1281   fprintf(fh, "%.2f %.2f X\n", p->x, p->y);
1282}
1283
1284void
1285EPS::xsect(const img_point *p, double angle, double d1, double d2)
1286{
1287    double s = sin(rad(angle));
1288    double c = cos(rad(angle));
1289    fprintf(fh, "%.2f %.2f M %.2f %.2f R\n",
1290            p->x - s * d2, p->y - c * d2,
1291            s * (d1 + d2), c * (d1 + d2));
1292}
1293
1294void
1295EPS::wall(const img_point *p, double angle, double d)
1296{
1297    double s = sin(rad(angle));
1298    double c = cos(rad(angle));
1299    fprintf(fh, "%.2f %.2f %c\n", p->x + s * d, p->y + c * d, first ? 'M' : 'L');
1300    first = false;
1301}
1302
1303void
1304EPS::passage(const img_point *p, double angle, double d1, double d2)
1305{
1306    double s = sin(rad(angle));
1307    double c = cos(rad(angle));
1308    double x1 = p->x + s * d1;
1309    double y1 = p->y + c * d1;
1310    double x2 = p->x - s * d2;
1311    double y2 = p->y - c * d2;
1312    fprintf(fh, "%.2f %.2f %c\n", x1, y1, first ? 'P' : 'L');
1313    first = false;
1314    psg.push_back(make_pair(x2, y2));
1315}
1316
1317void
1318EPS::tube_end()
1319{
1320    if (!psg.empty()) {
1321        vector<pair<double, double>>::const_reverse_iterator i;
1322        for (i = psg.rbegin(); i != psg.rend(); ++i) {
1323            fprintf(fh, "%.2f %.2f L\n", i->first, i->second);
1324        }
1325        fputs("F\n", fh);
1326        psg.clear();
1327    }
1328}
1329
1330void
1331EPS::footer(void)
1332{
1333   fputs("stroke showpage grestore\n"
1334         "%%Trailer\n"
1335         "cleartomark countdictstack exch sub { end } repeat restore\n"
1336         "%%EOF\n", fh);
1337}
1338
1339class UseNumericCLocale {
1340    char * current_locale;
1341
1342  public:
1343    UseNumericCLocale() {
1344        current_locale = osstrdup(setlocale(LC_NUMERIC, NULL));
1345        setlocale(LC_NUMERIC, "C");
1346    }
1347
1348    ~UseNumericCLocale() {
1349        setlocale(LC_NUMERIC, current_locale);
1350        osfree(current_locale);
1351    }
1352};
1353
1354static void
1355transform_point(const Point& pos, const Vector3* pre_offset,
1356                double COS, double SIN, double COST, double SINT,
1357                img_point* p)
1358{
1359    double x = pos.GetX();
1360    double y = pos.GetY();
1361    double z = pos.GetZ();
1362    if (pre_offset) {
1363        x += pre_offset->GetX();
1364        y += pre_offset->GetY();
1365        z += pre_offset->GetZ();
1366    }
1367    p->x = x * COS - y * SIN;
1368    double tmp = x * SIN + y * COS;
1369    p->y = z * COST - tmp * SINT;
1370    p->z = -(z * SINT + tmp * COST);
1371}
1372
1373bool
1374Export(const wxString &fnm_out, const wxString &title,
1375       const wxString &datestamp,
1376       const Model& model,
1377       const SurveyFilter* filter,
1378       double pan, double tilt, int show_mask, export_format format,
1379       double grid_, double text_height, double marker_size_,
1380       double scale)
1381{
1382   UseNumericCLocale dummy;
1383   int fPendingMove = 0;
1384   img_point p, p1;
1385   const int *pass;
1386   double SIN = sin(rad(pan));
1387   double COS = cos(rad(pan));
1388   double SINT = sin(rad(tilt));
1389   double COST = cos(rad(tilt));
1390
1391   grid = (show_mask & GRID) ? grid_ : 0.0;
1392   marker_size = marker_size_;
1393
1394   // Do we need to calculate min and max for each dimension?
1395   bool need_bounds = true;
1396   ExportFilter * filt;
1397   switch (format) {
1398       case FMT_3D:
1399           filt = new Export3D(model.GetCSProj(), model.GetSeparator());
1400           show_mask |= FULL_COORDS;
1401           need_bounds = false;
1402           break;
1403       case FMT_CSV:
1404           filt = new POS(model.GetSeparator(), true);
1405           show_mask |= FULL_COORDS;
1406           need_bounds = false;
1407           break;
1408       case FMT_DXF:
1409           filt = new DXF(text_height);
1410           break;
1411       case FMT_EPS:
1412           filt = new EPS(scale);
1413           break;
1414       case FMT_GPX:
1415           filt = new GPX(model.GetCSProj().c_str());
1416           show_mask |= FULL_COORDS;
1417           need_bounds = false;
1418           break;
1419       case FMT_HPGL:
1420           filt = new HPGL;
1421           // factor = POINTS_PER_MM * 1000.0 / scale;
1422           // HPGL doesn't use the bounds itself, but they are needed to set
1423           // the origin to the centre of lower left.
1424           break;
1425       case FMT_JSON:
1426           filt = new JSON;
1427           break;
1428       case FMT_KML: {
1429           bool clamp_to_ground = (show_mask & CLAMP_TO_GROUND);
1430           filt = new KML(model.GetCSProj().c_str(), clamp_to_ground);
1431           show_mask |= FULL_COORDS;
1432           need_bounds = false;
1433           break;
1434       }
1435       case FMT_PLT:
1436           filt = new PLT;
1437           show_mask |= FULL_COORDS;
1438           break;
1439       case FMT_POS:
1440           filt = new POS(model.GetSeparator(), false);
1441           show_mask |= FULL_COORDS;
1442           need_bounds = false;
1443           break;
1444       case FMT_SK:
1445           filt = new Skencil(scale);
1446           break;
1447       case FMT_SVG:
1448           filt = new SVG(scale, text_height);
1449           break;
1450       default:
1451           return false;
1452   }
1453
1454   if (!filt->fopen(fnm_out)) {
1455       delete filt;
1456       return false;
1457   }
1458
1459   const Vector3* pre_offset = NULL;
1460   if (show_mask & FULL_COORDS) {
1461        pre_offset = &(model.GetOffset());
1462   }
1463
1464   /* Get bounding box */
1465   double min_x, min_y, min_z, max_x, max_y, max_z;
1466   min_x = min_y = min_z = HUGE_VAL;
1467   max_x = max_y = max_z = -HUGE_VAL;
1468   if (need_bounds) {
1469        for (int f = 0; f != 8; ++f) {
1470            if ((show_mask & (f & img_FLAG_SURFACE) ? SURF : LEGS) == 0) {
1471                // Not showing traverse because of surface/underground status.
1472                continue;
1473            }
1474            if ((f & img_FLAG_SPLAY) && (show_mask & SPLAYS) == 0) {
1475                // Not showing because it's a splay.
1476                continue;
1477            }
1478            list<traverse>::const_iterator trav = model.traverses_begin(f, filter);
1479            list<traverse>::const_iterator tend = model.traverses_end(f);
1480            for ( ; trav != tend; trav = model.traverses_next(f, filter, trav)) {
1481                vector<PointInfo>::const_iterator pos = trav->begin();
1482                vector<PointInfo>::const_iterator end = trav->end();
1483                for ( ; pos != end; ++pos) {
1484                    transform_point(*pos, pre_offset, COS, SIN, COST, SINT, &p);
1485
1486                    if (p.x < min_x) min_x = p.x;
1487                    if (p.x > max_x) max_x = p.x;
1488                    if (p.y < min_y) min_y = p.y;
1489                    if (p.y > max_y) max_y = p.y;
1490                    if (p.z < min_z) min_z = p.z;
1491                    if (p.z > max_z) max_z = p.z;
1492                }
1493            }
1494        }
1495        list<LabelInfo*>::const_iterator pos = model.GetLabels();
1496        list<LabelInfo*>::const_iterator end = model.GetLabelsEnd();
1497        for ( ; pos != end; ++pos) {
1498            if (filter && !filter->CheckVisible((*pos)->GetText()))
1499                continue;
1500
1501            transform_point(**pos, pre_offset, COS, SIN, COST, SINT, &p);
1502
1503            if (p.x < min_x) min_x = p.x;
1504            if (p.x > max_x) max_x = p.x;
1505            if (p.y < min_y) min_y = p.y;
1506            if (p.y > max_y) max_y = p.y;
1507            if (p.z < min_z) min_z = p.z;
1508            if (p.z > max_z) max_z = p.z;
1509        }
1510
1511        if (grid > 0) {
1512            min_x -= grid / 2;
1513            max_x += grid / 2;
1514            min_y -= grid / 2;
1515            max_y += grid / 2;
1516        }
1517   }
1518
1519   /* Handle empty file and gracefully, and also zero for the !need_bounds
1520    * case. */
1521   if (min_x > max_x) {
1522      min_x = min_y = min_z = 0;
1523      max_x = max_y = max_z = 0;
1524   }
1525
1526   double x_offset, y_offset, z_offset;
1527   if (show_mask & FULL_COORDS) {
1528       // Full coordinates - offset is applied before rotations.
1529       x_offset = y_offset = z_offset = 0.0;
1530   } else if (show_mask & CENTRED) {
1531       // Centred.
1532       x_offset = (min_x + max_x) * -0.5;
1533       y_offset = (min_y + max_y) * -0.5;
1534       z_offset = (min_z + max_z) * -0.5;
1535   } else {
1536       // Origin at lowest SW corner.
1537       x_offset = -min_x;
1538       y_offset = -min_y;
1539       z_offset = -min_z;
1540   }
1541   if (need_bounds) {
1542        min_x += x_offset;
1543        max_x += x_offset;
1544        min_y += y_offset;
1545        max_y += y_offset;
1546        min_z += z_offset;
1547        max_z += z_offset;
1548   }
1549
1550   /* Header */
1551   filt->header(title.utf8_str(), datestamp.utf8_str(), model.GetDateStamp(),
1552                min_x, min_y, min_z, max_x, max_y, max_z);
1553
1554   p1.x = p1.y = p1.z = 0; /* avoid compiler warning */
1555
1556   for (pass = filt->passes(); *pass; ++pass) {
1557      int pass_mask = show_mask & *pass;
1558      if (!pass_mask)
1559          continue;
1560      filt->start_pass(*pass);
1561      if (pass_mask & (LEGS|SURF)) {
1562          for (int f = 0; f != 8; ++f) {
1563              unsigned flags = (f & img_FLAG_SURFACE) ? SURF : LEGS;
1564              if ((pass_mask & flags) == 0) {
1565                  // Not showing traverse because of surface/underground status.
1566                  continue;
1567              }
1568              if ((f & img_FLAG_SPLAY) && (show_mask & SPLAYS) == 0) {
1569                  // Not showing because it's a splay.
1570                  continue;
1571              }
1572              if (f & img_FLAG_SPLAY) flags |= SPLAYS;
1573              list<traverse>::const_iterator trav = model.traverses_begin(f, filter);
1574              list<traverse>::const_iterator tend = model.traverses_end(f);
1575              for ( ; trav != tend; trav = model.traverses_next(f, filter, trav)) {
1576                  assert(trav->size() > 1);
1577                  vector<PointInfo>::const_iterator pos = trav->begin();
1578                  vector<PointInfo>::const_iterator end = trav->end();
1579                  for ( ; pos != end; ++pos) {
1580                      transform_point(*pos, pre_offset, COS, SIN, COST, SINT, &p);
1581                      p.x += x_offset;
1582                      p.y += y_offset;
1583                      p.z += z_offset;
1584
1585                      if (pos == trav->begin()) {
1586                          // First point is move...
1587                          fPendingMove = 1;
1588                      } else {
1589                          filt->line(&p1, &p, flags, fPendingMove);
1590                          fPendingMove = 0;
1591                      }
1592                      p1 = p;
1593                  }
1594              }
1595          }
1596      }
1597      if (pass_mask & (STNS|LABELS|ENTS|FIXES|EXPORTS)) {
1598          list<LabelInfo*>::const_iterator pos = model.GetLabels();
1599          list<LabelInfo*>::const_iterator end = model.GetLabelsEnd();
1600          for ( ; pos != end; ++pos) {
1601              if (filter && !filter->CheckVisible((*pos)->GetText()))
1602                  continue;
1603
1604              transform_point(**pos, pre_offset, COS, SIN, COST, SINT, &p);
1605              p.x += x_offset;
1606              p.y += y_offset;
1607              p.z += z_offset;
1608
1609              int type = 0;
1610              if ((pass_mask & ENTS) && (*pos)->IsEntrance()) {
1611                  type = ENTS;
1612              } else if ((pass_mask & FIXES) && (*pos)->IsFixedPt()) {
1613                  type = FIXES;
1614              } else if ((pass_mask & EXPORTS) && (*pos)->IsExportedPt())  {
1615                  type = EXPORTS;
1616              } else if (pass_mask & LABELS) {
1617                  type = LABELS;
1618              }
1619              /* Use !UNDERGROUND as the criterion - we want stations where a
1620               * surface and underground survey meet to be in the underground
1621               * layer */
1622              bool f_surface = !(*pos)->IsUnderground();
1623              if (type) {
1624                  filt->label(&p, (*pos)->GetText(), f_surface, type);
1625              }
1626              if (pass_mask & STNS) {
1627                  filt->cross(&p, (*pos)->GetText(), f_surface);
1628              }
1629          }
1630      }
1631      if (pass_mask & (XSECT|WALLS|PASG)) {
1632          bool elevation = (tilt == 0.0);
1633          list<vector<XSect>>::const_iterator tube = model.tubes_begin();
1634          list<vector<XSect>>::const_iterator tube_end = model.tubes_end();
1635          for ( ; tube != tube_end; ++tube) {
1636              vector<XSect>::const_iterator pos = tube->begin();
1637              vector<XSect>::const_iterator end = tube->end();
1638              size_t active_tube_len = 0;
1639              for ( ; pos != end; ++pos) {
1640                  const XSect & xs = *pos;
1641                  // FIXME: This filtering can create tubes containing a single
1642                  // cross-section, which otherwise don't exist in aven (the
1643                  // Model class currently filters them out).  Perhaps we
1644                  // should just always include these - a single set of LRUD
1645                  // measurements is useful even if a single cross-section
1646                  // 3D tube perhaps isn't.
1647                  if (filter && !filter->CheckVisible(xs.GetLabel())) {
1648                      // Close any active tube.
1649                      if (active_tube_len > 0) {
1650                          active_tube_len = 0;
1651                          filt->tube_end();
1652                      }
1653                      continue;
1654                  }
1655
1656                  ++active_tube_len;
1657                  transform_point(xs.GetPoint(), pre_offset, COS, SIN, COST, SINT, &p);
1658                  p.x += x_offset;
1659                  p.y += y_offset;
1660                  p.z += z_offset;
1661
1662                  if (elevation) {
1663                      if (pass_mask & XSECT)
1664                          filt->xsect(&p, 90, xs.GetU(), xs.GetD());
1665                      if (pass_mask & WALL1)
1666                          filt->wall(&p, 90, xs.GetU());
1667                      if (pass_mask & WALL2)
1668                          filt->wall(&p, 270, xs.GetD());
1669                      if (pass_mask & PASG)
1670                          filt->passage(&p, 90, xs.GetU(), xs.GetD());
1671                  } else {
1672                      // Should only be enabled in plan or elevation mode.
1673                      double angle = xs.get_right_bearing() - pan;
1674                      if (pass_mask & XSECT)
1675                          filt->xsect(&p, angle + 180, xs.GetL(), xs.GetR());
1676                      if (pass_mask & WALL1)
1677                          filt->wall(&p, angle + 180, xs.GetL());
1678                      if (pass_mask & WALL2)
1679                          filt->wall(&p, angle, xs.GetR());
1680                      if (pass_mask & PASG)
1681                          filt->passage(&p, angle + 180, xs.GetL(), xs.GetR());
1682                  }
1683              }
1684              if (active_tube_len > 0) {
1685                  filt->tube_end();
1686              }
1687          }
1688      }
1689   }
1690   filt->footer();
1691   delete filt;
1692   osfree(htab);
1693   htab = NULL;
1694   return true;
1695}
Note: See TracBrowser for help on using the repository browser.