source: git/src/export.cc @ 76cf7f1

RELEASE/1.2debug-cidebug-ci-sanitiserswalls-data
Last change on this file since 76cf7f1 was 76cf7f1, checked in by Olly Betts <olly@…>, 5 years ago

Fix rotated exports

When exporting to a format where we support rotation in the horizontal
plane (such as SVG), the rotation was incorrectly applied to cross
section data (except for the default rotation of zero).

Fixes #108, reported by Richard Knapp.

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