source: git/src/export.cc

Last change on this file was b3b0900, checked in by Olly Betts <olly@…>, 19 hours ago

Clean up header includes

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