source: git/src/printing.cc @ a4e8876

stereo-2025
Last change on this file since a4e8876 was 1854ba1, checked in by Olly Betts <olly@…>, 11 months ago

Remove Skencil export support

The last release of Skencil was in 2005. There was an attempt to
revive the project in 2010, but that didn't lead to another release
and seems to have petered out. No current Linux distro (or other
package system) seems to have packages for it. The current git
version still appears to require Python 2.

  • Property mode set to 100644
File size: 66.9 KB
Line 
1/* printing.cc */
2/* Aven printing code */
3/* Copyright (C) 1993-2003,2004,2005,2006,2010,2011,2012,2013,2014,2015,2016,2017,2018 Olly Betts
4 * Copyright (C) 2001,2004 Philip Underwood
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 */
20
21#include <config.h>
22
23#include <wx/confbase.h>
24#include <wx/filename.h>
25#include <wx/print.h>
26#include <wx/printdlg.h>
27#include <wx/spinctrl.h>
28#include <wx/radiobox.h>
29#include <wx/statbox.h>
30#include <wx/valgen.h>
31
32#include <vector>
33
34#include <stdio.h>
35#include <stdlib.h>
36#include <math.h>
37#include <string.h>
38#include <ctype.h>
39#include <float.h>
40#include <limits.h>
41
42#include "export.h"
43#include "filelist.h"
44#include "filename.h"
45#include "message.h"
46#include "useful.h"
47
48#include "aven.h"
49#include "avenprcore.h"
50#include "mainfrm.h"
51#include "printing.h"
52
53using namespace std;
54
55// How many decimal points to show on angles:
56#define ANGLE_DP 1
57
58#if ANGLE_DP == 0
59# define ANGLE_FMT wxT("%03.f")
60# define ANGLE2_FMT wxT("%.f")
61#elif ANGLE_DP == 1
62# define ANGLE_FMT wxT("%05.1f")
63# define ANGLE2_FMT wxT("%.1f")
64#elif ANGLE_DP == 2
65# define ANGLE_FMT wxT("%06.2f")
66# define ANGLE2_FMT wxT("%.2f")
67#else
68# error Need to add ANGLE_FMT and ANGLE2_FMT for the currently set ANGLE_DP
69#endif
70
71static wxString
72format_angle(const wxChar * fmt, double angle)
73{
74    wxString s;
75    s.Printf(fmt, angle);
76    size_t dot = s.find('.');
77    size_t i = s.size();
78    while (i > dot) {
79        --i;
80        if (s[i] != '0') {
81            if (i != dot) ++i;
82            s.resize(i);
83            break;
84        }
85    }
86    s += wmsg(/*°*/344);
87    return s;
88}
89
90enum {
91        svx_EXPORT = 1200,
92        svx_FORMAT,
93        svx_SCALE,
94        svx_BEARING,
95        svx_TILT,
96        svx_LEGS,
97        svx_STATIONS,
98        svx_NAMES,
99        svx_XSECT,
100        svx_WALLS,
101        svx_PASSAGES,
102        svx_BORDERS,
103        svx_BLANKS,
104        svx_LEGEND,
105        svx_SURFACE,
106        svx_SPLAYS,
107        svx_PLAN,
108        svx_ELEV,
109        svx_ENTS,
110        svx_FIXES,
111        svx_EXPORTS,
112        svx_GRID,
113        svx_TEXT_HEIGHT,
114        svx_MARKER_SIZE,
115        svx_CENTRED,
116        svx_FULLCOORDS,
117        svx_CLAMP_TO_GROUND
118};
119
120class BitValidator : public wxValidator {
121    // Disallow assignment.
122    BitValidator & operator=(const BitValidator&);
123
124  protected:
125    int * val;
126
127    int mask;
128
129  public:
130    BitValidator(int * val_, int mask_)
131        : val(val_), mask(mask_) { }
132
133    BitValidator(const BitValidator &o) : wxValidator() {
134        Copy(o);
135    }
136
137    ~BitValidator() { }
138
139    wxObject *Clone() const { return new BitValidator(val, mask); }
140
141    bool Copy(const BitValidator& o) {
142        wxValidator::Copy(o);
143        val = o.val;
144        mask = o.mask;
145        return true;
146    }
147
148    bool Validate(wxWindow *) { return true; }
149
150    bool TransferToWindow() {
151        if (!m_validatorWindow->IsKindOf(CLASSINFO(wxCheckBox)))
152            return false;
153        ((wxCheckBox*)m_validatorWindow)->SetValue(*val & mask);
154        return true;
155    }
156
157    bool TransferFromWindow() {
158        if (!m_validatorWindow->IsKindOf(CLASSINFO(wxCheckBox)))
159            return false;
160        if (((wxCheckBox*)m_validatorWindow)->IsChecked())
161            *val |= mask;
162        else
163            *val &= ~mask;
164        return true;
165    }
166};
167
168class svxPrintout : public wxPrintout {
169    MainFrm *mainfrm;
170    layout *m_layout;
171    wxPageSetupDialogData* m_data;
172    wxDC* pdc;
173    wxFont *font_labels, *font_default;
174    // Currently unused, but "skip blank pages" would use it.
175    bool scan_for_blank_pages;
176
177    wxPen *pen_frame, *pen_cross, *pen_leg, *pen_surface_leg, *pen_splay;
178    wxColour colour_text, colour_labels;
179
180    long x_t, y_t;
181    double font_scaling_x, font_scaling_y;
182
183    struct {
184        long x_min, y_min, x_max, y_max;
185    } clip;
186
187    bool fBlankPage;
188
189    int check_intersection(long x_p, long y_p);
190    void draw_info_box();
191    void draw_scale_bar(double x, double y, double MaxLength);
192    int next_page(int *pstate, char **q, int pageLim);
193    void drawticks(int tsize, int x, int y);
194
195    void MOVEMM(double X, double Y) {
196        MoveTo((long)(X * m_layout->scX), (long)(Y * m_layout->scY));
197    }
198    void DRAWMM(double X, double Y) {
199        DrawTo((long)(X * m_layout->scX), (long)(Y * m_layout->scY));
200    }
201    void MoveTo(long x, long y);
202    void DrawTo(long x, long y);
203    void DrawCross(long x, long y);
204    void SetFont(wxFont * font) {
205        pdc->SetFont(*font);
206    }
207    void WriteString(const wxString & s);
208    void DrawEllipse(long x, long y, long r, long R);
209    void SolidRectangle(long x, long y, long w, long h);
210    void NewPage(int pg, int pagesX, int pagesY);
211    void PlotLR(const vector<XSect> & centreline);
212    void PlotUD(const vector<XSect> & centreline);
213  public:
214    svxPrintout(MainFrm *mainfrm, layout *l, wxPageSetupDialogData *data, const wxString & title);
215    bool OnPrintPage(int pageNum);
216    void GetPageInfo(int *minPage, int *maxPage,
217                     int *pageFrom, int *pageTo);
218    bool HasPage(int pageNum);
219    void OnBeginPrinting();
220    void OnEndPrinting();
221};
222
223BEGIN_EVENT_TABLE(svxPrintDlg, wxDialog)
224    EVT_CHOICE(svx_FORMAT, svxPrintDlg::OnChange)
225    EVT_TEXT(svx_SCALE, svxPrintDlg::OnChangeScale)
226    EVT_COMBOBOX(svx_SCALE, svxPrintDlg::OnChangeScale)
227    EVT_SPINCTRLDOUBLE(svx_BEARING, svxPrintDlg::OnChangeSpin)
228    EVT_SPINCTRLDOUBLE(svx_TILT, svxPrintDlg::OnChangeSpin)
229    EVT_BUTTON(wxID_PRINT, svxPrintDlg::OnPrint)
230    EVT_BUTTON(svx_EXPORT, svxPrintDlg::OnExport)
231    EVT_BUTTON(wxID_CANCEL, svxPrintDlg::OnCancel)
232#ifdef AVEN_PRINT_PREVIEW
233    EVT_BUTTON(wxID_PREVIEW, svxPrintDlg::OnPreview)
234#endif
235    EVT_BUTTON(svx_PLAN, svxPrintDlg::OnPlan)
236    EVT_BUTTON(svx_ELEV, svxPrintDlg::OnElevation)
237    EVT_UPDATE_UI(svx_PLAN, svxPrintDlg::OnPlanUpdate)
238    EVT_UPDATE_UI(svx_ELEV, svxPrintDlg::OnElevationUpdate)
239    EVT_CHECKBOX(svx_LEGS, svxPrintDlg::OnChange)
240    EVT_CHECKBOX(svx_STATIONS, svxPrintDlg::OnChange)
241    EVT_CHECKBOX(svx_NAMES, svxPrintDlg::OnChange)
242    EVT_CHECKBOX(svx_SURFACE, svxPrintDlg::OnChange)
243    EVT_CHECKBOX(svx_SPLAYS, svxPrintDlg::OnChange)
244    EVT_CHECKBOX(svx_ENTS, svxPrintDlg::OnChange)
245    EVT_CHECKBOX(svx_FIXES, svxPrintDlg::OnChange)
246    EVT_CHECKBOX(svx_EXPORTS, svxPrintDlg::OnChange)
247END_EVENT_TABLE()
248
249static wxString scales[] = {
250    wxT(""),
251    wxT("25"),
252    wxT("50"),
253    wxT("100"),
254    wxT("250"),
255    wxT("500"),
256    wxT("1000"),
257    wxT("2500"),
258    wxT("5000"),
259    wxT("10000"),
260    wxT("25000"),
261    wxT("50000"),
262    wxT("100000"),
263    wxT("...")
264};
265
266// The order of these arrays must match export_format in export.h.
267
268static wxString formats[] = {
269    wxT("Survex 3d"),
270    wxT("CSV"),
271    wxT("DXF"),
272    wxT("EPS"),
273    wxT("GPX"),
274    wxT("HPGL"),
275    wxT("JSON"),
276    wxT("KML"),
277    wxT("Plot"),
278    wxT("Survex pos"),
279    wxT("SVG")
280};
281
282static_assert(sizeof(formats) == FMT_MAX_PLUS_ONE_ * sizeof(formats[0]),
283              "formats[] matches enum export_format");
284
285// We discriminate as "One page" isn't valid for exporting.
286static wxString default_scale_print;
287static wxString default_scale_export;
288
289svxPrintDlg::svxPrintDlg(MainFrm* mainfrm_, const wxString & filename,
290                         const wxString & title,
291                         const wxString & datestamp,
292                         double angle, double tilt_angle,
293                         bool labels, bool crosses, bool legs, bool surf,
294                         bool splays, bool tubes, bool ents, bool fixes,
295                         bool exports, bool printing, bool close_after_)
296        : wxDialog(mainfrm_, wxID_ANY,
297                   wxString(printing ? /* TRANSLATORS: Title of the print
298                                        * dialog */
299                                       wmsg(/*Print*/399) :
300                                       /* TRANSLATORS: Title of the export
301                                        * dialog */
302                                       wmsg(/*Export*/383))),
303          m_layout(printing ? wxGetApp().GetPageSetupDialogData() : NULL),
304          m_File(filename), mainfrm(mainfrm_), close_after(close_after_)
305{
306    m_scale = NULL;
307    m_printSize = NULL;
308    m_bearing = NULL;
309    m_tilt = NULL;
310    m_format = NULL;
311    int show_mask = 0;
312    if (labels)
313        show_mask |= LABELS;
314    if (crosses)
315        show_mask |= STNS;
316    if (legs)
317        show_mask |= LEGS;
318    if (surf)
319        show_mask |= SURF;
320    if (splays)
321        show_mask |= SPLAYS;
322    if (tubes)
323        show_mask |= XSECT|WALLS|PASG;
324    if (ents)
325        show_mask |= ENTS;
326    if (fixes)
327        show_mask |= FIXES;
328    if (exports)
329        show_mask |= EXPORTS;
330    m_layout.show_mask = show_mask;
331    m_layout.datestamp = datestamp;
332    m_layout.rot = angle;
333    m_layout.title = title;
334    if (mainfrm->IsExtendedElevation()) {
335        m_layout.view = layout::EXTELEV;
336        if (m_layout.rot != 0.0 && m_layout.rot != 180.0) m_layout.rot = 0;
337        m_layout.tilt = 0;
338    } else {
339        m_layout.tilt = tilt_angle;
340        if (m_layout.tilt == -90.0) {
341            m_layout.view = layout::PLAN;
342        } else if (m_layout.tilt == 0.0) {
343            m_layout.view = layout::ELEV;
344        } else {
345            m_layout.view = layout::TILT;
346        }
347    }
348
349    /* setup our print dialog*/
350    wxBoxSizer* v1 = new wxBoxSizer(wxVERTICAL);
351    wxBoxSizer* h1 = new wxBoxSizer(wxHORIZONTAL); // holds controls
352    /* TRANSLATORS: Used as a label for the surrounding box for the "Bearing"
353     * and "Tilt angle" fields, and the "Plan view" and "Elevation" buttons in
354     * the "what to print/export" dialog. */
355    m_viewbox = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, wmsg(/*View*/283)), wxVERTICAL);
356    /* TRANSLATORS: Used as a label for the surrounding box for the "survey
357     * legs" "stations" "names" etc checkboxes in the "what to print" dialog.
358     * "Elements" isn’t a good name for this but nothing better has yet come to
359     * mind! */
360    wxBoxSizer* v2 = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, wmsg(/*Elements*/256)), wxVERTICAL);
361    wxBoxSizer* h2 = new wxBoxSizer(wxHORIZONTAL); // holds buttons
362
363    if (!printing) {
364        wxStaticText* label;
365        label = new wxStaticText(this, wxID_ANY, wxString(wmsg(/*Export format*/410)));
366        const size_t n_formats = sizeof(formats) / sizeof(formats[0]);
367        m_format = new wxChoice(this, svx_FORMAT,
368                                wxDefaultPosition, wxDefaultSize,
369                                n_formats, formats);
370        unsigned current_format = 0;
371        wxConfigBase * cfg = wxConfigBase::Get();
372        wxString s;
373        if (cfg->Read(wxT("export_format"), &s, wxString())) {
374            for (unsigned i = 0; i != n_formats; ++i) {
375                if (s == formats[i]) {
376                    current_format = i;
377                    break;
378                }
379            }
380        }
381        m_format->SetSelection(current_format);
382        wxBoxSizer* formatbox = new wxBoxSizer(wxHORIZONTAL);
383        formatbox->Add(label, 0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
384        formatbox->Add(m_format, 0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
385
386        v1->Add(formatbox, 0, wxALIGN_LEFT|wxALL, 0);
387    }
388
389    wxStaticText* label;
390    label = new wxStaticText(this, wxID_ANY, wxString(wmsg(/*Scale*/154)) + wxT(" 1:"));
391    if (printing && scales[0].empty()) {
392        /* TRANSLATORS: used in the scale drop down selector in the print
393         * dialog the implicit meaning is "choose a suitable scale to fit
394         * the plot on a single page", but we need something shorter */
395        scales[0].assign(wmsg(/*One page*/258));
396    }
397    wxString default_scale;
398    if (printing) {
399        default_scale = default_scale_print;
400        if (default_scale.empty()) default_scale = scales[0];
401    } else {
402        default_scale = default_scale_export;
403        if (default_scale.empty()) default_scale = wxT("1000");
404    }
405    const wxString* scale_list = scales;
406    size_t n_scales = sizeof(scales) / sizeof(scales[0]);
407    if (!printing) {
408        ++scale_list;
409        --n_scales;
410    }
411    m_scale = new wxComboBox(this, svx_SCALE, default_scale, wxDefaultPosition,
412                             wxDefaultSize, n_scales, scale_list);
413    m_scalebox = new wxBoxSizer(wxHORIZONTAL);
414    m_scalebox->Add(label, 0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
415    m_scalebox->Add(m_scale, 0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
416
417    m_viewbox->Add(m_scalebox, 0, wxALIGN_LEFT|wxALL, 0);
418
419    if (printing) {
420        // Make the dummy string wider than any sane value and use that to
421        // fix the width of the control so the sizers allow space for bigger
422        // page layouts.
423        m_printSize = new wxStaticText(this, wxID_ANY, wxString::Format(wmsg(/*%d pages (%dx%d)*/257), 9604, 98, 98));
424        m_viewbox->Add(m_printSize, 0, wxALIGN_LEFT|wxALL, 5);
425    }
426
427    if (m_layout.view != layout::EXTELEV) {
428        wxFlexGridSizer* anglebox = new wxFlexGridSizer(2);
429        wxStaticText * brg_label, * tilt_label;
430        brg_label = new wxStaticText(this, wxID_ANY, wmsg(/*Bearing*/259));
431        anglebox->Add(brg_label, 0, wxALIGN_CENTRE_VERTICAL|wxALIGN_LEFT|wxALL, 5);
432        // wSP_WRAP means that you can scroll past 360 to 0, and vice versa.
433        m_bearing = new wxSpinCtrlDouble(this, svx_BEARING, wxEmptyString,
434                wxDefaultPosition, wxDefaultSize,
435                wxSP_ARROW_KEYS|wxALIGN_RIGHT|wxSP_WRAP);
436        m_bearing->SetRange(0.0, 360.0);
437        m_bearing->SetDigits(ANGLE_DP);
438        anglebox->Add(m_bearing, 0, wxALIGN_CENTRE|wxALL, 5);
439        /* TRANSLATORS: Used in the print dialog: */
440        tilt_label = new wxStaticText(this, wxID_ANY, wmsg(/*Tilt angle*/263));
441        anglebox->Add(tilt_label, 0, wxALIGN_CENTRE_VERTICAL|wxALIGN_LEFT|wxALL, 5);
442        m_tilt = new wxSpinCtrlDouble(this, svx_TILT, wxEmptyString,
443                wxDefaultPosition, wxDefaultSize,
444                wxSP_ARROW_KEYS|wxALIGN_RIGHT);
445        m_tilt->SetRange(-90.0, 90.0);
446        m_tilt->SetDigits(ANGLE_DP);
447        anglebox->Add(m_tilt, 0, wxALIGN_CENTRE|wxALL, 5);
448
449        m_viewbox->Add(anglebox, 0, wxALIGN_LEFT|wxALL, 0);
450
451        wxBoxSizer * planelevsizer = new wxBoxSizer(wxHORIZONTAL);
452        planelevsizer->Add(new wxButton(this, svx_PLAN, wmsg(/*P&lan view*/117)),
453                           0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
454        planelevsizer->Add(new wxButton(this, svx_ELEV, wmsg(/*&Elevation*/285)),
455                           0, wxALIGN_CENTRE_VERTICAL|wxALL, 5);
456
457        m_viewbox->Add(planelevsizer, 0, wxALIGN_LEFT|wxALL, 5);
458    }
459
460    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
461     * "survey stations". */
462    v2->Add(new wxCheckBox(this, svx_LEGS, wmsg(/*Underground Survey Legs*/262),
463                           wxDefaultPosition, wxDefaultSize, 0,
464                           BitValidator(&m_layout.show_mask, LEGS)),
465            0, wxALIGN_LEFT|wxALL, 2);
466    /* TRANSLATORS: Here a "survey leg" is a set of measurements between two
467     * "survey stations". */
468    v2->Add(new wxCheckBox(this, svx_SURFACE, wmsg(/*Sur&face Survey Legs*/403),
469                           wxDefaultPosition, wxDefaultSize, 0,
470                           BitValidator(&m_layout.show_mask, SURF)),
471            0, wxALIGN_LEFT|wxALL, 2);
472    v2->Add(new wxCheckBox(this, svx_SPLAYS, wmsg(/*Spla&y Legs*/406),
473                           wxDefaultPosition, wxDefaultSize, 0,
474                           BitValidator(&m_layout.show_mask, SPLAYS)),
475            0, wxALIGN_LEFT|wxALL, 2);
476    v2->Add(new wxCheckBox(this, svx_STATIONS, wmsg(/*Crosses*/261),
477                           wxDefaultPosition, wxDefaultSize, 0,
478                           BitValidator(&m_layout.show_mask, STNS)),
479            0, wxALIGN_LEFT|wxALL, 2);
480    v2->Add(new wxCheckBox(this, svx_NAMES, wmsg(/*Station Names*/260),
481                           wxDefaultPosition, wxDefaultSize, 0,
482                           BitValidator(&m_layout.show_mask, LABELS)),
483            0, wxALIGN_LEFT|wxALL, 2);
484    v2->Add(new wxCheckBox(this, svx_ENTS, wmsg(/*Entrances*/418),
485                           wxDefaultPosition, wxDefaultSize, 0,
486                           BitValidator(&m_layout.show_mask, ENTS)),
487            0, wxALIGN_LEFT|wxALL, 2);
488    v2->Add(new wxCheckBox(this, svx_FIXES, wmsg(/*Fixed Points*/419),
489                           wxDefaultPosition, wxDefaultSize, 0,
490                           BitValidator(&m_layout.show_mask, FIXES)),
491            0, wxALIGN_LEFT|wxALL, 2);
492    v2->Add(new wxCheckBox(this, svx_EXPORTS, wmsg(/*Exported Stations*/420),
493                           wxDefaultPosition, wxDefaultSize, 0,
494                           BitValidator(&m_layout.show_mask, EXPORTS)),
495            0, wxALIGN_LEFT|wxALL, 2);
496    v2->Add(new wxCheckBox(this, svx_XSECT, wmsg(/*Cross-sections*/393),
497                           wxDefaultPosition, wxDefaultSize, 0,
498                           BitValidator(&m_layout.show_mask, XSECT)),
499            0, wxALIGN_LEFT|wxALL, 2);
500    if (!printing) {
501        v2->Add(new wxCheckBox(this, svx_WALLS, wmsg(/*Walls*/394),
502                               wxDefaultPosition, wxDefaultSize, 0,
503                               BitValidator(&m_layout.show_mask, WALLS)),
504                0, wxALIGN_LEFT|wxALL, 2);
505        // TRANSLATORS: Label for checkbox which controls whether there's a
506        // layer in the exported file (for formats such as DXF and SVG)
507        // containing polygons for the inside of cave passages).
508        v2->Add(new wxCheckBox(this, svx_PASSAGES, wmsg(/*Passages*/395),
509                               wxDefaultPosition, wxDefaultSize, 0,
510                               BitValidator(&m_layout.show_mask, PASG)),
511                0, wxALIGN_LEFT|wxALL, 2);
512        v2->Add(new wxCheckBox(this, svx_CENTRED, wmsg(/*Origin in centre*/421),
513                               wxDefaultPosition, wxDefaultSize, 0,
514                               BitValidator(&m_layout.show_mask, CENTRED)),
515                0, wxALIGN_LEFT|wxALL, 2);
516        v2->Add(new wxCheckBox(this, svx_FULLCOORDS, wmsg(/*Full coordinates*/422),
517                               wxDefaultPosition, wxDefaultSize, 0,
518                               BitValidator(&m_layout.show_mask, FULL_COORDS)),
519                0, wxALIGN_LEFT|wxALL, 2);
520        v2->Add(new wxCheckBox(this, svx_CLAMP_TO_GROUND, wmsg(/*Clamp to ground*/477),
521                               wxDefaultPosition, wxDefaultSize, 0,
522                               BitValidator(&m_layout.show_mask, CLAMP_TO_GROUND)),
523                0, wxALIGN_LEFT|wxALL, 2);
524    }
525    if (printing) {
526        /* TRANSLATORS: used in the print dialog - controls drawing lines
527         * around each page */
528        v2->Add(new wxCheckBox(this, svx_BORDERS, wmsg(/*Page Borders*/264),
529                               wxDefaultPosition, wxDefaultSize, 0,
530                               wxGenericValidator(&m_layout.Border)),
531                0, wxALIGN_LEFT|wxALL, 2);
532        /* TRANSLATORS: will be used in the print dialog - check this to print
533         * blank pages (otherwise they’ll be skipped to save paper) */
534//      m_blanks = new wxCheckBox(this, svx_BLANKS, wmsg(/*Blank Pages*/266));
535//      v2->Add(m_blanks, 0, wxALIGN_LEFT|wxALL, 2);
536        /* TRANSLATORS: As in the legend on a map.  Used in the print dialog -
537         * controls drawing the box at the lower left with survey name, view
538         * angles, etc */
539        v2->Add(new wxCheckBox(this, svx_LEGEND, wmsg(/*Legend*/265),
540                               wxDefaultPosition, wxDefaultSize, 0,
541                               wxGenericValidator(&m_layout.Legend)),
542                0, wxALIGN_LEFT|wxALL, 2);
543    }
544
545    h1->Add(v2, 0, wxALIGN_LEFT|wxALL, 5);
546    h1->Add(m_viewbox, 0, wxALIGN_LEFT|wxLEFT, 5);
547
548    v1->Add(h1, 0, wxALIGN_LEFT|wxALL, 5);
549
550    // When we enable/disable checkboxes in the export dialog, ideally we'd
551    // like the dialog to resize, but not sure how to achieve that, so we
552    // add a stretchable spacer here so at least the buttons stay in the
553    // lower right corner.
554    v1->AddStretchSpacer();
555
556    wxButton * but;
557    but = new wxButton(this, wxID_CANCEL);
558    h2->Add(but, 0, wxALL, 5);
559    if (printing) {
560#ifdef AVEN_PRINT_PREVIEW
561        but = new wxButton(this, wxID_PREVIEW);
562        h2->Add(but, 0, wxALL, 5);
563        but = new wxButton(this, wxID_PRINT);
564#else
565        but = new wxButton(this, wxID_PRINT, wmsg(/*&Print...*/400));
566#endif
567    } else {
568        /* TRANSLATORS: The text on the action button in the "Export" settings
569         * dialog */
570        but = new wxButton(this, svx_EXPORT, wmsg(/*&Export...*/230));
571    }
572    but->SetDefault();
573    h2->Add(but, 0, wxALL, 5);
574    v1->Add(h2, 0, wxALIGN_RIGHT|wxALL, 5);
575
576    SetAutoLayout(true);
577    SetSizer(v1);
578    v1->SetSizeHints(this);
579
580    LayoutToUI();
581    SomethingChanged(0);
582}
583
584void
585svxPrintDlg::OnPrint(wxCommandEvent&) {
586    SomethingChanged(0);
587    TransferDataFromWindow();
588    wxPageSetupDialogData * psdd = wxGetApp().GetPageSetupDialogData();
589    wxPrintDialogData pd(psdd->GetPrintData());
590    wxPrinter pr(&pd);
591    svxPrintout po(mainfrm, &m_layout, psdd, m_File);
592    if (m_layout.SkipBlank) {
593        // FIXME: wx's printing requires a contiguous range of valid page
594        // numbers.  To achieve that, we need to run a scan for blank pages
595        // here, so that GetPageInfo() knows what range to return, and so
596        // that OnPrintPage() can map a page number back to where in the
597        // MxN multi-page layout.
598#if 0
599        po.scan_for_blank_pages = true;
600        for (int page = 1; page <= m_layout->pages; ++page) {
601            po.fBlankPage = true;
602            po.OnPrintPage(page);
603            // FIXME: Do something with po.fBlankPage
604        }
605        po.scan_for_blank_pages = false;
606#endif
607    }
608    if (pr.Print(this, &po, true)) {
609        // Close the print dialog if printing succeeded.
610        Destroy();
611    }
612}
613
614void
615svxPrintDlg::OnExport(wxCommandEvent&) {
616    UIToLayout();
617    TransferDataFromWindow();
618    wxString leaf;
619    wxFileName::SplitPath(m_File, NULL, NULL, &leaf, NULL, wxPATH_NATIVE);
620    unsigned format_idx = ((wxChoice*)FindWindow(svx_FORMAT))->GetSelection();
621    const auto& info = export_format_info[format_idx];
622    leaf += wxString::FromUTF8(info.extension);
623
624    wxString filespec = wmsg(info.msg_filetype);
625    filespec += wxT("|*");
626    filespec += wxString::FromUTF8(info.extension);
627    filespec += wxT("|");
628    filespec += wmsg(/*All files*/208);
629    filespec += wxT("|");
630    filespec += wxFileSelectorDefaultWildcardStr;
631
632    /* TRANSLATORS: Title of file dialog to choose name and type of exported
633     * file. */
634    wxFileDialog dlg(this, wmsg(/*Export as:*/401), wxString(), leaf,
635                     filespec, wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
636    if (dlg.ShowModal() == wxID_OK) {
637        /* FIXME: Set up a way for the user to specify these: */
638        double grid = DEFAULT_GRID_SPACING; // metres
639        double text_height = DEFAULT_TEXT_HEIGHT;
640        double marker_size = DEFAULT_MARKER_SIZE;
641
642        try {
643            const wxString& export_fnm = dlg.GetPath();
644            unsigned mask = info.mask;
645            double rot, tilt;
646            if (mask & ORIENTABLE) {
647                rot = m_layout.rot;
648                tilt = m_layout.tilt;
649            } else {
650                rot = 0.0;
651                tilt = -90.0;
652            }
653            if (!Export(export_fnm, m_layout.title,
654                        m_layout.datestamp, *mainfrm, mainfrm->GetTreeFilter(),
655                        rot, tilt, m_layout.get_effective_show_mask(),
656                        export_format(format_idx),
657                        grid, text_height, marker_size, m_layout.Scale)) {
658                wxString m = wxString::Format(wmsg(/*Couldn’t write file “%s”*/402).c_str(),
659                                              export_fnm.c_str());
660                wxGetApp().ReportError(m);
661            }
662        } catch (const wxString & m) {
663            wxGetApp().ReportError(m);
664        }
665    }
666    Destroy();
667}
668
669#ifdef AVEN_PRINT_PREVIEW
670void
671svxPrintDlg::OnPreview(wxCommandEvent&) {
672    SomethingChanged(0);
673    TransferDataFromWindow();
674    wxPageSetupDialogData * psdd = wxGetApp().GetPageSetupDialogData();
675    wxPrintDialogData pd(psdd->GetPrintData());
676    wxPrintPreview* pv;
677    pv = new wxPrintPreview(new svxPrintout(mainfrm, &m_layout, psdd, m_File),
678                            new svxPrintout(mainfrm, &m_layout, psdd, m_File),
679                            &pd);
680    // TRANSLATORS: Title of the print preview dialog
681    wxPreviewFrame *frame = new wxPreviewFrame(pv, mainfrm, wmsg(/*Print Preview*/398));
682    frame->Initialize();
683
684    // Size preview frame so that all of the controlbar and canvas can be seen
685    // if possible.
686    int w, h;
687    // GetBestSize gives us the width needed to show the whole controlbar.
688    frame->GetBestSize(&w, &h);
689    if (h < w) {
690        // On wxGTK at least, GetBestSize() returns much too small a height.
691        h = w * 6 / 5;
692    }
693    // Ensure that we don't make the window bigger than the screen.
694    // Use wxGetClientDisplayRect() so we don't cover the MS Windows
695    // task bar either.
696    wxRect disp = wxGetClientDisplayRect();
697    if (w > disp.GetWidth()) w = disp.GetWidth();
698    if (h > disp.GetHeight()) h = disp.GetHeight();
699    // Centre the window within the "ClientDisplayRect".
700    int x = disp.GetLeft() + (disp.GetWidth() - w) / 2;
701    int y = disp.GetTop() + (disp.GetHeight() - h) / 2;
702    frame->SetSize(x, y, w, h);
703
704    frame->Show();
705}
706#endif
707
708void
709svxPrintDlg::OnPlan(wxCommandEvent&) {
710    m_tilt->SetValue(-90.0);
711    SomethingChanged(svx_TILT);
712}
713
714void
715svxPrintDlg::OnElevation(wxCommandEvent&) {
716    m_tilt->SetValue(0.0);
717    SomethingChanged(svx_TILT);
718}
719
720void
721svxPrintDlg::OnPlanUpdate(wxUpdateUIEvent& e) {
722    e.Enable(m_tilt->GetValue() != -90.0);
723}
724
725void
726svxPrintDlg::OnElevationUpdate(wxUpdateUIEvent& e) {
727    e.Enable(m_tilt->GetValue() != 0.0);
728}
729
730void
731svxPrintDlg::OnChangeSpin(wxSpinDoubleEvent& e) {
732    SomethingChanged(e.GetId());
733}
734
735void
736svxPrintDlg::OnChange(wxCommandEvent& e) {
737    SomethingChanged(e.GetId());
738}
739
740void
741svxPrintDlg::OnChangeScale(wxCommandEvent& e) {
742    // Seems to be needed on macOS.
743    if (!m_scale) return;
744    wxString value = m_scale->GetValue();
745    if (value == "...") {
746        m_scale->SetValue("");
747        m_scale->SetFocus();
748    } else {
749        default_scale_print = value;
750        if (default_scale_print != scales[0]) {
751            // Don't store "One page" for use when exporting.
752            default_scale_export = default_scale_print;
753        }
754    }
755    SomethingChanged(e.GetId());
756}
757
758void
759svxPrintDlg::OnCancel(wxCommandEvent&) {
760    if (close_after)
761        mainfrm->Close();
762    Destroy();
763}
764
765void
766svxPrintDlg::SomethingChanged(int control_id) {
767    if ((control_id == 0 || control_id == svx_FORMAT) && m_format) {
768        // Update the shown/hidden fields for the newly selected export filter.
769        int new_filter_idx = m_format->GetSelection();
770        if (new_filter_idx != wxNOT_FOUND) {
771            unsigned mask = export_format_info[new_filter_idx].mask;
772            static const struct { int id; unsigned mask; } controls[] = {
773                { svx_LEGS, LEGS },
774                { svx_SURFACE, SURF },
775                { svx_SPLAYS, SPLAYS },
776                { svx_STATIONS, STNS },
777                { svx_NAMES, LABELS },
778                { svx_XSECT, XSECT },
779                { svx_WALLS, WALLS },
780                { svx_PASSAGES, PASG },
781                { svx_ENTS, ENTS },
782                { svx_FIXES, FIXES },
783                { svx_EXPORTS, EXPORTS },
784                { svx_CENTRED, CENTRED },
785                { svx_FULLCOORDS, FULL_COORDS },
786                { svx_CLAMP_TO_GROUND, CLAMP_TO_GROUND },
787            };
788            static unsigned n_controls = sizeof(controls) / sizeof(controls[0]);
789            for (unsigned i = 0; i != n_controls; ++i) {
790                wxWindow * control = FindWindow(controls[i].id);
791                if (control) control->Show(mask & controls[i].mask);
792            }
793            m_scalebox->Show(bool(mask & SCALE));
794            m_viewbox->Show(bool(mask & ORIENTABLE));
795            GetSizer()->Layout();
796            // Force the window to resize to match the updated layout.
797            if (control_id) SetSizerAndFit(GetSizer());
798            if (control_id == svx_FORMAT) {
799                wxConfigBase * cfg = wxConfigBase::Get();
800                cfg->Write(wxT("export_format"), formats[new_filter_idx]);
801            }
802        }
803    }
804
805    UIToLayout();
806
807    if (m_printSize || m_scale) {
808        // Update the bounding box.
809        RecalcBounds();
810
811        if (m_scale) {
812            if (!(m_scale->GetValue()).ToDouble(&(m_layout.Scale)) ||
813                m_layout.Scale == 0.0) {
814                m_layout.pick_scale(1, 1);
815            }
816        }
817    }
818
819    if (m_printSize && m_layout.xMax >= m_layout.xMin) {
820        m_layout.pages_required();
821        m_printSize->SetLabel(wxString::Format(wmsg(/*%d pages (%dx%d)*/257), m_layout.pages, m_layout.pagesX, m_layout.pagesY));
822    }
823}
824
825void
826svxPrintDlg::LayoutToUI()
827{
828//    m_blanks->SetValue(m_layout.SkipBlank);
829    if (m_layout.view != layout::EXTELEV) {
830        m_tilt->SetValue(m_layout.tilt);
831        m_bearing->SetValue(m_layout.rot);
832    }
833
834    if (m_scale && m_layout.Scale != 0) {
835        // Do this last as it causes an OnChange message which calls UIToLayout
836        wxString temp;
837        temp << m_layout.Scale;
838        m_scale->SetValue(temp);
839    }
840}
841
842void
843svxPrintDlg::UIToLayout()
844{
845//    m_layout.SkipBlank = m_blanks->IsChecked();
846
847    if (m_layout.view != layout::EXTELEV && m_tilt) {
848        m_layout.tilt = m_tilt->GetValue();
849        if (m_layout.tilt == -90.0) {
850            m_layout.view = layout::PLAN;
851        } else if (m_layout.tilt == 0.0) {
852            m_layout.view = layout::ELEV;
853        } else {
854            m_layout.view = layout::TILT;
855        }
856
857        bool enable_passage_opts = (m_layout.view != layout::TILT);
858        wxWindow * win;
859        win = FindWindow(svx_XSECT);
860        if (win) win->Enable(enable_passage_opts);
861        win = FindWindow(svx_WALLS);
862        if (win) win->Enable(enable_passage_opts);
863        win = FindWindow(svx_PASSAGES);
864        if (win) win->Enable(enable_passage_opts);
865
866        m_layout.rot = m_bearing->GetValue();
867    }
868}
869
870void
871svxPrintDlg::RecalcBounds()
872{
873    m_layout.yMax = m_layout.xMax = -DBL_MAX;
874    m_layout.yMin = m_layout.xMin = DBL_MAX;
875
876    double SIN = sin(rad(m_layout.rot));
877    double COS = cos(rad(m_layout.rot));
878    double SINT = sin(rad(m_layout.tilt));
879    double COST = cos(rad(m_layout.tilt));
880
881    const SurveyFilter* filter = mainfrm->GetTreeFilter();
882    int show_mask = m_layout.get_effective_show_mask();
883    if (show_mask & LEGS) {
884        for (int f = 0; f != 8; ++f) {
885            if ((show_mask & (f & img_FLAG_SURFACE) ? SURF : LEGS) == 0) {
886                // Not showing traverse because of surface/underground status.
887                continue;
888            }
889            if ((f & img_FLAG_SPLAY) && (show_mask & SPLAYS) == 0) {
890                // Not showing because it's a splay.
891                continue;
892            }
893            list<traverse>::const_iterator trav = mainfrm->traverses_begin(f, filter);
894            list<traverse>::const_iterator tend = mainfrm->traverses_end(f);
895            for ( ; trav != tend; trav = mainfrm->traverses_next(f, filter, trav)) {
896                vector<PointInfo>::const_iterator pos = trav->begin();
897                vector<PointInfo>::const_iterator end = trav->end();
898                for ( ; pos != end; ++pos) {
899                    double x = pos->GetX();
900                    double y = pos->GetY();
901                    double z = pos->GetZ();
902                    double X = x * COS - y * SIN;
903                    if (X > m_layout.xMax) m_layout.xMax = X;
904                    if (X < m_layout.xMin) m_layout.xMin = X;
905                    double Y = z * COST - (x * SIN + y * COS) * SINT;
906                    if (Y > m_layout.yMax) m_layout.yMax = Y;
907                    if (Y < m_layout.yMin) m_layout.yMin = Y;
908                }
909            }
910        }
911    }
912
913    if ((show_mask & XSECT) &&
914        (m_layout.tilt == 0.0 || m_layout.tilt == 90.0 || m_layout.tilt == -90.0)) {
915        list<vector<XSect>>::const_iterator trav = mainfrm->tubes_begin();
916        list<vector<XSect>>::const_iterator tend = mainfrm->tubes_end();
917        for ( ; trav != tend; ++trav) {
918            const XSect* prev_pt_v = NULL;
919            Vector3 last_right(1.0, 0.0, 0.0);
920
921            vector<XSect>::const_iterator i = trav->begin();
922            vector<XSect>::size_type segment = 0;
923            while (i != trav->end()) {
924                // get the coordinates of this vertex
925                const XSect & pt_v = *i++;
926                if (m_layout.tilt == 0.0) {
927                    double u = pt_v.GetU();
928                    double d = pt_v.GetD();
929
930                    if (u >= 0 || d >= 0) {
931                        if (filter && !filter->CheckVisible(pt_v.GetLabel()))
932                            continue;
933
934                        double x = pt_v.GetX();
935                        double y = pt_v.GetY();
936                        double z = pt_v.GetZ();
937                        double X = x * COS - y * SIN;
938                        double Y = z * COST - (x * SIN + y * COS) * SINT;
939
940                        if (X > m_layout.xMax) m_layout.xMax = X;
941                        if (X < m_layout.xMin) m_layout.xMin = X;
942                        double U = Y + max(0.0, pt_v.GetU());
943                        if (U > m_layout.yMax) m_layout.yMax = U;
944                        double D = Y - max(0.0, pt_v.GetD());
945                        if (D < m_layout.yMin) m_layout.yMin = D;
946                    }
947                } else {
948                    // More complex, and this duplicates the algorithm from
949                    // PlotLR() - we should try to share that, maybe via a
950                    // template.
951                    Vector3 right;
952
953                    const Vector3 up_v(0.0, 0.0, 1.0);
954
955                    if (segment == 0) {
956                        assert(i != trav->end());
957                        // first segment
958
959                        // get the coordinates of the next vertex
960                        const XSect & next_pt_v = *i;
961
962                        // calculate vector from this pt to the next one
963                        Vector3 leg_v = next_pt_v - pt_v;
964
965                        // obtain a vector in the LRUD plane
966                        right = leg_v * up_v;
967                        if (right.magnitude() == 0) {
968                            right = last_right;
969                        } else {
970                            last_right = right;
971                        }
972                    } else if (segment + 1 == trav->size()) {
973                        // last segment
974
975                        // Calculate vector from the previous pt to this one.
976                        Vector3 leg_v = pt_v - *prev_pt_v;
977
978                        // Obtain a horizontal vector in the LRUD plane.
979                        right = leg_v * up_v;
980                        if (right.magnitude() == 0) {
981                            right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
982                        } else {
983                            last_right = right;
984                        }
985                    } else {
986                        assert(i != trav->end());
987                        // Intermediate segment.
988
989                        // Get the coordinates of the next vertex.
990                        const XSect & next_pt_v = *i;
991
992                        // Calculate vectors from this vertex to the
993                        // next vertex, and from the previous vertex to
994                        // this one.
995                        Vector3 leg1_v = pt_v - *prev_pt_v;
996                        Vector3 leg2_v = next_pt_v - pt_v;
997
998                        // Obtain horizontal vectors perpendicular to
999                        // both legs, then normalise and average to get
1000                        // a horizontal bisector.
1001                        Vector3 r1 = leg1_v * up_v;
1002                        Vector3 r2 = leg2_v * up_v;
1003                        r1.normalise();
1004                        r2.normalise();
1005                        right = r1 + r2;
1006                        if (right.magnitude() == 0) {
1007                            // This is the "mid-pitch" case...
1008                            right = last_right;
1009                        }
1010                        last_right = right;
1011                    }
1012
1013                    // Scale to unit vectors in the LRUD plane.
1014                    right.normalise();
1015
1016                    double l = pt_v.GetL();
1017                    double r = pt_v.GetR();
1018
1019                    if (l >= 0 || r >= 0) {
1020                        if (!filter || filter->CheckVisible(pt_v.GetLabel())) {
1021                            // Get the x and y coordinates of the survey station
1022                            double pt_X = pt_v.GetX() * COS - pt_v.GetY() * SIN;
1023                            double pt_Y = pt_v.GetX() * SIN + pt_v.GetY() * COS;
1024
1025                            double X, Y;
1026                            if (l >= 0) {
1027                                // Get the x and y coordinates of the end of the left arrow
1028                                Vector3 p = pt_v.GetPoint() - right * l;
1029                                X = p.GetX() * COS - p.GetY() * SIN;
1030                                Y = (p.GetX() * SIN + p.GetY() * COS);
1031                            } else {
1032                                X = pt_X;
1033                                Y = pt_Y;
1034                            }
1035                            if (X > m_layout.xMax) m_layout.xMax = X;
1036                            if (X < m_layout.xMin) m_layout.xMin = X;
1037                            if (Y > m_layout.yMax) m_layout.yMax = Y;
1038                            if (Y < m_layout.yMin) m_layout.yMin = Y;
1039
1040                            if (r >= 0) {
1041                                // Get the x and y coordinates of the end of the right arrow
1042                                Vector3 p = pt_v.GetPoint() + right * r;
1043                                X = p.GetX() * COS - p.GetY() * SIN;
1044                                Y = (p.GetX() * SIN + p.GetY() * COS);
1045                            } else {
1046                                X = pt_X;
1047                                Y = pt_Y;
1048                            }
1049                            if (X > m_layout.xMax) m_layout.xMax = X;
1050                            if (X < m_layout.xMin) m_layout.xMin = X;
1051                            if (Y > m_layout.yMax) m_layout.yMax = Y;
1052                            if (Y < m_layout.yMin) m_layout.yMin = Y;
1053                        }
1054                    }
1055
1056                    prev_pt_v = &pt_v;
1057
1058                    ++segment;
1059                }
1060            }
1061        }
1062    }
1063
1064    if (show_mask & (LABELS|STNS)) {
1065        for (auto label = mainfrm->GetLabels();
1066             label != mainfrm->GetLabelsEnd();
1067             ++label) {
1068            if (filter && !filter->CheckVisible((*label)->GetText()))
1069                continue;
1070            double x = (*label)->GetX();
1071            double y = (*label)->GetY();
1072            double z = (*label)->GetZ();
1073            if ((show_mask & SURF) || (*label)->IsUnderground()) {
1074                double X = x * COS - y * SIN;
1075                if (X > m_layout.xMax) m_layout.xMax = X;
1076                if (X < m_layout.xMin) m_layout.xMin = X;
1077                double Y = z * COST - (x * SIN + y * COS) * SINT;
1078                if (Y > m_layout.yMax) m_layout.yMax = Y;
1079                if (Y < m_layout.yMin) m_layout.yMin = Y;
1080            }
1081        }
1082    }
1083}
1084
1085static int xpPageWidth, ypPageDepth;
1086static long x_offset, y_offset;
1087static int fontsize, fontsize_labels;
1088
1089/* FIXME: allow the font to be set */
1090
1091static const char *fontname = "Arial", *fontname_labels = "Arial";
1092
1093svxPrintout::svxPrintout(MainFrm *mainfrm_, layout *l,
1094                         wxPageSetupDialogData *data, const wxString & title)
1095    : wxPrintout(title), font_labels(NULL), font_default(NULL),
1096      scan_for_blank_pages(false)
1097{
1098    mainfrm = mainfrm_;
1099    m_layout = l;
1100    m_data = data;
1101}
1102
1103void
1104svxPrintout::draw_info_box()
1105{
1106   layout *l = m_layout;
1107   int boxwidth = 70;
1108   int boxheight = 30;
1109
1110   pdc->SetPen(*pen_frame);
1111
1112   int div = boxwidth;
1113   if (l->view != layout::EXTELEV) {
1114      boxwidth += boxheight;
1115      MOVEMM(div, boxheight);
1116      DRAWMM(div, 0);
1117      MOVEMM(0, 30); DRAWMM(div, 30);
1118   }
1119
1120   MOVEMM(0, boxheight);
1121   DRAWMM(boxwidth, boxheight);
1122   DRAWMM(boxwidth, 0);
1123   if (!l->Border) {
1124      DRAWMM(0, 0);
1125      DRAWMM(0, boxheight);
1126   }
1127
1128   MOVEMM(0, 20); DRAWMM(div, 20);
1129   MOVEMM(0, 10); DRAWMM(div, 10);
1130
1131   switch (l->view) {
1132    case layout::PLAN: {
1133      long ax, ay, bx, by, cx, cy, dx, dy;
1134
1135      long xc = boxwidth - boxheight / 2;
1136      long yc = boxheight / 2;
1137      const double RADIUS = boxheight / 3;
1138      DrawEllipse(long(xc * l->scX), long(yc * l->scY),
1139                  long(RADIUS * l->scX), long(RADIUS * l->scY));
1140
1141      ax = (long)((xc - (RADIUS - 1) * sin(rad(000.0 + l->rot))) * l->scX);
1142      ay = (long)((yc + (RADIUS - 1) * cos(rad(000.0 + l->rot))) * l->scY);
1143      bx = (long)((xc - RADIUS * 0.5 * sin(rad(180.0 + l->rot))) * l->scX);
1144      by = (long)((yc + RADIUS * 0.5 * cos(rad(180.0 + l->rot))) * l->scY);
1145      cx = (long)((xc - (RADIUS - 1) * sin(rad(160.0 + l->rot))) * l->scX);
1146      cy = (long)((yc + (RADIUS - 1) * cos(rad(160.0 + l->rot))) * l->scY);
1147      dx = (long)((xc - (RADIUS - 1) * sin(rad(200.0 + l->rot))) * l->scX);
1148      dy = (long)((yc + (RADIUS - 1) * cos(rad(200.0 + l->rot))) * l->scY);
1149
1150      MoveTo(ax, ay);
1151      DrawTo(bx, by);
1152      DrawTo(cx, cy);
1153      DrawTo(ax, ay);
1154      DrawTo(dx, dy);
1155      DrawTo(bx, by);
1156
1157      pdc->SetTextForeground(colour_text);
1158      MOVEMM(div + 0.5, boxheight - 5.5);
1159      WriteString(wmsg(/*North*/115));
1160
1161      wxString angle = format_angle(ANGLE_FMT, l->rot);
1162      wxString s;
1163      /* TRANSLATORS: This is used on printouts of plans, with %s replaced by
1164       * something like "123°".  The bearing is up the page. */
1165      s.Printf(wmsg(/*Plan view, %s up page*/168), angle.c_str());
1166      MOVEMM(2, 12); WriteString(s);
1167      break;
1168    }
1169    case layout::ELEV: case layout::TILT: {
1170      const int L = div + 2;
1171      const int R = boxwidth - 2;
1172      const int H = boxheight / 2;
1173      MOVEMM(L, H); DRAWMM(L + 5, H - 3); DRAWMM(L + 3, H); DRAWMM(L + 5, H + 3);
1174
1175      DRAWMM(L, H); DRAWMM(R, H);
1176
1177      DRAWMM(R - 5, H + 3); DRAWMM(R - 3, H); DRAWMM(R - 5, H - 3); DRAWMM(R, H);
1178
1179      MOVEMM((L + R) / 2, H - 2); DRAWMM((L + R) / 2, H + 2);
1180
1181      pdc->SetTextForeground(colour_text);
1182      MOVEMM(div + 2, boxheight - 8);
1183      /* TRANSLATORS: "Elevation on" 020 <-> 200 degrees */
1184      WriteString(wmsg(/*Elevation on*/116));
1185
1186      MOVEMM(L, 2);
1187      WriteString(format_angle(ANGLE_FMT, fmod(l->rot + 270.0, 360.0)));
1188      MOVEMM(R - 10, 2);
1189      WriteString(format_angle(ANGLE_FMT, fmod(l->rot + 90.0, 360.0)));
1190
1191      wxString angle = format_angle(ANGLE_FMT, l->rot);
1192      wxString s;
1193      if (l->view == layout::ELEV) {
1194          /* TRANSLATORS: This is used on printouts of elevations, with %s
1195           * replaced by something like "123°".  The bearing is the direction
1196           * we’re looking. */
1197          s.Printf(wmsg(/*Elevation facing %s*/169), angle.c_str());
1198      } else {
1199          wxString a2 = format_angle(ANGLE2_FMT, l->tilt);
1200          /* TRANSLATORS: This is used on printouts of tilted elevations, with
1201           * the first %s replaced by something like "123°", and the second by
1202           * something like "-45°".  The bearing is the direction we’re
1203           * looking. */
1204          s.Printf(wmsg(/*Elevation facing %s, tilted %s*/284), angle.c_str(), a2.c_str());
1205      }
1206      MOVEMM(2, 12); WriteString(s);
1207      break;
1208    }
1209    case layout::EXTELEV:
1210      pdc->SetTextForeground(colour_text);
1211      MOVEMM(2, 12);
1212      /* TRANSLATORS: This is used on printouts of extended elevations. */
1213      WriteString(wmsg(/*Extended elevation*/191));
1214      break;
1215   }
1216
1217   MOVEMM(2, boxheight - 8); WriteString(l->title);
1218
1219   MOVEMM(2, 2);
1220   // FIXME: "Original Scale" better?
1221   WriteString(wxString::Format(wmsg(/*Scale*/154) + wxT(" 1:%.0f"),
1222                                l->Scale));
1223
1224   /* This used to be a copyright line, but it was occasionally
1225    * mis-interpreted as us claiming copyright on the survey, so let's
1226    * give the website URL instead */
1227   MOVEMM(boxwidth + 2, 2);
1228   WriteString(wxT("Survex " VERSION " - https://survex.com/"));
1229
1230   draw_scale_bar(boxwidth + 10.0, 17.0, l->PaperWidth - boxwidth - 18.0);
1231}
1232
1233/* Draw fancy scale bar with bottom left at (x,y) (both in mm) and at most */
1234/* MaxLength mm long. The scaling in use is 1:scale */
1235void
1236svxPrintout::draw_scale_bar(double x, double y, double MaxLength)
1237{
1238   double StepEst, d;
1239   int E, Step, n, c;
1240   wxString buf;
1241   /* Limit scalebar to 20cm to stop people with A0 plotters complaining */
1242   if (MaxLength > 200.0) MaxLength = 200.0;
1243
1244#define dmin 10.0      /* each division >= dmin mm long */
1245#define StepMax 5      /* number in steps of at most StepMax (x 10^N) */
1246#define epsilon (1e-4) /* fudge factor to prevent rounding problems */
1247
1248   E = (int)ceil(log10((dmin * 0.001 * m_layout->Scale) / StepMax));
1249   StepEst = pow(10.0, -(double)E) * (dmin * 0.001) * m_layout->Scale - epsilon;
1250
1251   /* Force labelling to be in multiples of 1, 2, or 5 */
1252   Step = (StepEst <= 1.0 ? 1 : (StepEst <= 2.0 ? 2 : 5));
1253
1254   /* Work out actual length of each scale bar division */
1255   d = Step * pow(10.0, (double)E) / m_layout->Scale * 1000.0;
1256
1257   /* FIXME: Non-metric units here... */
1258   /* Choose appropriate units, s.t. if possible E is >=0 and minimized */
1259   int units;
1260   if (E >= 3) {
1261      E -= 3;
1262      units = /*km*/423;
1263   } else if (E >= 0) {
1264      units = /*m*/424;
1265   } else {
1266      E += 2;
1267      units = /*cm*/425;
1268   }
1269
1270   buf = wmsg(/*Scale*/154);
1271
1272   /* Add units used - eg. "Scale (10m)" */
1273   double pow10_E = pow(10.0, (double)E);
1274   if (E >= 0) {
1275      buf += wxString::Format(wxT(" (%.f%s)"), pow10_E, wmsg(units).c_str());
1276   } else {
1277      int sf = -(int)floor(E);
1278      buf += wxString::Format(wxT(" (%.*f%s)"), sf, pow10_E, wmsg(units).c_str());
1279   }
1280   pdc->SetTextForeground(colour_text);
1281   MOVEMM(x, y + 4); WriteString(buf);
1282
1283   /* Work out how many divisions there will be */
1284   n = (int)(MaxLength / d);
1285
1286   pdc->SetPen(*pen_frame);
1287
1288   long Y = long(y * m_layout->scY);
1289   long Y2 = long((y + 3) * m_layout->scY);
1290   long X = long(x * m_layout->scX);
1291   long X2 = long((x + n * d) * m_layout->scX);
1292
1293   /* Draw top of scale bar */
1294   MoveTo(X2, Y2);
1295   DrawTo(X, Y2);
1296#if 0
1297   DrawTo(X2, Y);
1298   DrawTo(X, Y);
1299   MOVEMM(x + n * d, y); DRAWMM(x, y);
1300#endif
1301   /* Draw divisions and label them */
1302   for (c = 0; c <= n; c++) {
1303      pdc->SetPen(*pen_frame);
1304      X = long((x + c * d) * m_layout->scX);
1305      MoveTo(X, Y);
1306      DrawTo(X, Y2);
1307#if 0 // Don't waste toner!
1308      /* Draw a "zebra crossing" scale bar. */
1309      if (c < n && (c & 1) == 0) {
1310          X2 = long((x + (c + 1) * d) * m_layout->scX);
1311          SolidRectangle(X, Y, X2 - X, Y2 - Y);
1312      }
1313#endif
1314      buf.Printf(wxT("%d"), c * Step);
1315      pdc->SetTextForeground(colour_text);
1316      MOVEMM(x + c * d - buf.length(), y - 5);
1317      WriteString(buf);
1318   }
1319}
1320
1321#if 0
1322void
1323make_calibration(layout *l) {
1324      img_point pt = { 0.0, 0.0, 0.0 };
1325      l->xMax = l->yMax = 0.1;
1326      l->xMin = l->yMin = 0;
1327
1328      stack(l, img_MOVE, NULL, &pt);
1329      pt.x = 0.1;
1330      stack(l, img_LINE, NULL, &pt);
1331      pt.y = 0.1;
1332      stack(l, img_LINE, NULL, &pt);
1333      pt.x = 0.0;
1334      stack(l, img_LINE, NULL, &pt);
1335      pt.y = 0.0;
1336      stack(l, img_LINE, NULL, &pt);
1337      pt.x = 0.05;
1338      pt.y = 0.001;
1339      stack(l, img_LABEL, "10cm", &pt);
1340      pt.x = 0.001;
1341      pt.y = 0.05;
1342      stack(l, img_LABEL, "10cm", &pt);
1343      l->Scale = 1.0;
1344}
1345#endif
1346
1347int
1348svxPrintout::next_page(int *pstate, char **q, int pageLim)
1349{
1350   char *p;
1351   int page;
1352   int c;
1353   p = *q;
1354   if (*pstate > 0) {
1355      /* doing a range */
1356      (*pstate)++;
1357      wxASSERT(*p == '-');
1358      p++;
1359      while (isspace((unsigned char)*p)) p++;
1360      if (sscanf(p, "%u%n", &page, &c) > 0) {
1361         p += c;
1362      } else {
1363         page = pageLim;
1364      }
1365      if (*pstate > page) goto err;
1366      if (*pstate < page) return *pstate;
1367      *q = p;
1368      *pstate = 0;
1369      return page;
1370   }
1371
1372   while (isspace((unsigned char)*p) || *p == ',') p++;
1373
1374   if (!*p) return 0; /* done */
1375
1376   if (*p == '-') {
1377      *q = p;
1378      *pstate = 1;
1379      return 1; /* range with initial parameter omitted */
1380   }
1381   if (sscanf(p, "%u%n", &page, &c) > 0) {
1382      p += c;
1383      while (isspace((unsigned char)*p)) p++;
1384      *q = p;
1385      if (0 < page && page <= pageLim) {
1386         if (*p == '-') *pstate = page; /* range with start */
1387         return page;
1388      }
1389   }
1390   err:
1391   *pstate = -1;
1392   return 0;
1393}
1394
1395/* Draws in alignment marks on each page or borders on edge pages */
1396void
1397svxPrintout::drawticks(int tsize, int x, int y)
1398{
1399   long i;
1400   int s = tsize * 4;
1401   int o = s / 8;
1402   bool fAtCorner = false;
1403   pdc->SetPen(*pen_frame);
1404   if (x == 0 && m_layout->Border) {
1405      /* solid left border */
1406      MoveTo(clip.x_min, clip.y_min);
1407      DrawTo(clip.x_min, clip.y_max);
1408      fAtCorner = true;
1409   } else {
1410      if (x > 0 || y > 0) {
1411         MoveTo(clip.x_min, clip.y_min);
1412         DrawTo(clip.x_min, clip.y_min + tsize);
1413      }
1414      if (s && x > 0 && m_layout->Cutlines) {
1415         /* dashed left border */
1416         i = (clip.y_max - clip.y_min) -
1417             (tsize + ((clip.y_max - clip.y_min - tsize * 2L) % s) / 2);
1418         for ( ; i > tsize; i -= s) {
1419            MoveTo(clip.x_min, clip.y_max - (i + o));
1420            DrawTo(clip.x_min, clip.y_max - (i - o));
1421         }
1422      }
1423      if (x > 0 || y < m_layout->pagesY - 1) {
1424         MoveTo(clip.x_min, clip.y_max - tsize);
1425         DrawTo(clip.x_min, clip.y_max);
1426         fAtCorner = true;
1427      }
1428   }
1429
1430   if (y == m_layout->pagesY - 1 && m_layout->Border) {
1431      /* solid top border */
1432      if (!fAtCorner) MoveTo(clip.x_min, clip.y_max);
1433      DrawTo(clip.x_max, clip.y_max);
1434      fAtCorner = true;
1435   } else {
1436      if (y < m_layout->pagesY - 1 || x > 0) {
1437         if (!fAtCorner) MoveTo(clip.x_min, clip.y_max);
1438         DrawTo(clip.x_min + tsize, clip.y_max);
1439      }
1440      if (s && y < m_layout->pagesY - 1 && m_layout->Cutlines) {
1441         /* dashed top border */
1442         i = (clip.x_max - clip.x_min) -
1443             (tsize + ((clip.x_max - clip.x_min - tsize * 2L) % s) / 2);
1444         for ( ; i > tsize; i -= s) {
1445            MoveTo(clip.x_max - (i + o), clip.y_max);
1446            DrawTo(clip.x_max - (i - o), clip.y_max);
1447         }
1448      }
1449      if (y < m_layout->pagesY - 1 || x < m_layout->pagesX - 1) {
1450         MoveTo(clip.x_max - tsize, clip.y_max);
1451         DrawTo(clip.x_max, clip.y_max);
1452         fAtCorner = true;
1453      } else {
1454         fAtCorner = false;
1455      }
1456   }
1457
1458   if (x == m_layout->pagesX - 1 && m_layout->Border) {
1459      /* solid right border */
1460      if (!fAtCorner) MoveTo(clip.x_max, clip.y_max);
1461      DrawTo(clip.x_max, clip.y_min);
1462      fAtCorner = true;
1463   } else {
1464      if (x < m_layout->pagesX - 1 || y < m_layout->pagesY - 1) {
1465         if (!fAtCorner) MoveTo(clip.x_max, clip.y_max);
1466         DrawTo(clip.x_max, clip.y_max - tsize);
1467      }
1468      if (s && x < m_layout->pagesX - 1 && m_layout->Cutlines) {
1469         /* dashed right border */
1470         i = (clip.y_max - clip.y_min) -
1471             (tsize + ((clip.y_max - clip.y_min - tsize * 2L) % s) / 2);
1472         for ( ; i > tsize; i -= s) {
1473            MoveTo(clip.x_max, clip.y_min + (i + o));
1474            DrawTo(clip.x_max, clip.y_min + (i - o));
1475         }
1476      }
1477      if (x < m_layout->pagesX - 1 || y > 0) {
1478         MoveTo(clip.x_max, clip.y_min + tsize);
1479         DrawTo(clip.x_max, clip.y_min);
1480         fAtCorner = true;
1481      } else {
1482         fAtCorner = false;
1483      }
1484   }
1485
1486   if (y == 0 && m_layout->Border) {
1487      /* solid bottom border */
1488      if (!fAtCorner) MoveTo(clip.x_max, clip.y_min);
1489      DrawTo(clip.x_min, clip.y_min);
1490   } else {
1491      if (y > 0 || x < m_layout->pagesX - 1) {
1492         if (!fAtCorner) MoveTo(clip.x_max, clip.y_min);
1493         DrawTo(clip.x_max - tsize, clip.y_min);
1494      }
1495      if (s && y > 0 && m_layout->Cutlines) {
1496         /* dashed bottom border */
1497         i = (clip.x_max - clip.x_min) -
1498             (tsize + ((clip.x_max - clip.x_min - tsize * 2L) % s) / 2);
1499         for ( ; i > tsize; i -= s) {
1500            MoveTo(clip.x_min + (i + o), clip.y_min);
1501            DrawTo(clip.x_min + (i - o), clip.y_min);
1502         }
1503      }
1504      if (y > 0 || x > 0) {
1505         MoveTo(clip.x_min + tsize, clip.y_min);
1506         DrawTo(clip.x_min, clip.y_min);
1507      }
1508   }
1509}
1510
1511bool
1512svxPrintout::OnPrintPage(int pageNum) {
1513    GetPageSizePixels(&xpPageWidth, &ypPageDepth);
1514    pdc = GetDC();
1515    pdc->SetBackgroundMode(wxTRANSPARENT);
1516#ifdef AVEN_PRINT_PREVIEW
1517    if (IsPreview()) {
1518        int dcx, dcy;
1519        pdc->GetSize(&dcx, &dcy);
1520        pdc->SetUserScale((double)dcx / xpPageWidth, (double)dcy / ypPageDepth);
1521    }
1522#endif
1523
1524    layout * l = m_layout;
1525    {
1526        int pwidth, pdepth;
1527        GetPageSizeMM(&pwidth, &pdepth);
1528        l->scX = (double)xpPageWidth / pwidth;
1529        l->scY = (double)ypPageDepth / pdepth;
1530        font_scaling_x = l->scX * (25.4 / 72.0);
1531        font_scaling_y = l->scY * (25.4 / 72.0);
1532        long MarginLeft = m_data->GetMarginTopLeft().x;
1533        long MarginTop = m_data->GetMarginTopLeft().y;
1534        long MarginBottom = m_data->GetMarginBottomRight().y;
1535        long MarginRight = m_data->GetMarginBottomRight().x;
1536        xpPageWidth -= (int)(l->scX * (MarginLeft + MarginRight));
1537        ypPageDepth -= (int)(l->scY * (FOOTER_HEIGHT_MM + MarginBottom + MarginTop));
1538        // xpPageWidth -= 1;
1539        pdepth -= FOOTER_HEIGHT_MM;
1540        x_offset = (long)(l->scX * MarginLeft);
1541        y_offset = (long)(l->scY * MarginTop);
1542        l->PaperWidth = pwidth -= MarginLeft + MarginRight;
1543        l->PaperDepth = pdepth -= MarginTop + MarginBottom;
1544    }
1545
1546    double SIN = sin(rad(l->rot));
1547    double COS = cos(rad(l->rot));
1548    double SINT = sin(rad(l->tilt));
1549    double COST = cos(rad(l->tilt));
1550
1551    NewPage(pageNum, l->pagesX, l->pagesY);
1552
1553    if (l->Legend && pageNum == (l->pagesY - 1) * l->pagesX + 1) {
1554        SetFont(font_default);
1555        draw_info_box();
1556    }
1557
1558    pdc->SetClippingRegion(x_offset, y_offset, xpPageWidth + 1, ypPageDepth + 1);
1559
1560    const double Sc = 1000 / l->Scale;
1561
1562    const SurveyFilter* filter = mainfrm->GetTreeFilter();
1563    int show_mask = l->get_effective_show_mask();
1564    if (show_mask & (LEGS|SURF)) {
1565        for (int f = 0; f != 8; ++f) {
1566            if ((show_mask & (f & img_FLAG_SURFACE) ? SURF : LEGS) == 0) {
1567                // Not showing traverse because of surface/underground status.
1568                continue;
1569            }
1570            if ((f & img_FLAG_SPLAY) && (show_mask & SPLAYS) == 0) {
1571                // Not showing because it's a splay.
1572                continue;
1573            }
1574            if (f & img_FLAG_SPLAY) {
1575                pdc->SetPen(*pen_splay);
1576            } else if (f & img_FLAG_SURFACE) {
1577                pdc->SetPen(*pen_surface_leg);
1578            } else {
1579                pdc->SetPen(*pen_leg);
1580            }
1581            list<traverse>::const_iterator trav = mainfrm->traverses_begin(f, filter);
1582            list<traverse>::const_iterator tend = mainfrm->traverses_end(f);
1583            for ( ; trav != tend; trav = mainfrm->traverses_next(f, filter, trav)) {
1584                vector<PointInfo>::const_iterator pos = trav->begin();
1585                vector<PointInfo>::const_iterator end = trav->end();
1586                for ( ; pos != end; ++pos) {
1587                    double x = pos->GetX();
1588                    double y = pos->GetY();
1589                    double z = pos->GetZ();
1590                    double X = x * COS - y * SIN;
1591                    double Y = z * COST - (x * SIN + y * COS) * SINT;
1592                    long px = (long)((X * Sc + l->xOrg) * l->scX);
1593                    long py = (long)((Y * Sc + l->yOrg) * l->scY);
1594                    if (pos == trav->begin()) {
1595                        MoveTo(px, py);
1596                    } else {
1597                        DrawTo(px, py);
1598                    }
1599                }
1600            }
1601        }
1602    }
1603
1604    if ((show_mask & XSECT) &&
1605        (l->tilt == 0.0 || l->tilt == 90.0 || l->tilt == -90.0)) {
1606        pdc->SetPen(*pen_splay);
1607        list<vector<XSect>>::const_iterator trav = mainfrm->tubes_begin();
1608        list<vector<XSect>>::const_iterator tend = mainfrm->tubes_end();
1609        for ( ; trav != tend; ++trav) {
1610            if (l->tilt == 0.0) {
1611                PlotUD(*trav);
1612            } else {
1613                // m_layout.tilt is 90.0 or -90.0 due to check above.
1614                PlotLR(*trav);
1615            }
1616        }
1617    }
1618
1619    if (show_mask & (LABELS|STNS)) {
1620        if (show_mask & LABELS) SetFont(font_labels);
1621        for (auto label = mainfrm->GetLabels();
1622             label != mainfrm->GetLabelsEnd();
1623             ++label) {
1624            if (filter && !filter->CheckVisible((*label)->GetText()))
1625                continue;
1626            double px = (*label)->GetX();
1627            double py = (*label)->GetY();
1628            double pz = (*label)->GetZ();
1629            if ((show_mask & SURF) || (*label)->IsUnderground()) {
1630                double X = px * COS - py * SIN;
1631                double Y = pz * COST - (px * SIN + py * COS) * SINT;
1632                long xnew, ynew;
1633                xnew = (long)((X * Sc + l->xOrg) * l->scX);
1634                ynew = (long)((Y * Sc + l->yOrg) * l->scY);
1635                if (show_mask & STNS) {
1636                    pdc->SetPen(*pen_cross);
1637                    DrawCross(xnew, ynew);
1638                }
1639                if (show_mask & LABELS) {
1640                    pdc->SetTextForeground(colour_labels);
1641                    MoveTo(xnew, ynew);
1642                    WriteString((*label)->GetText());
1643                }
1644            }
1645        }
1646    }
1647
1648    return true;
1649}
1650
1651void
1652svxPrintout::GetPageInfo(int *minPage, int *maxPage,
1653                         int *pageFrom, int *pageTo)
1654{
1655    *minPage = *pageFrom = 1;
1656    *maxPage = *pageTo = m_layout->pages;
1657}
1658
1659bool
1660svxPrintout::HasPage(int pageNum) {
1661    return (pageNum <= m_layout->pages);
1662}
1663
1664void
1665svxPrintout::OnBeginPrinting() {
1666    /* Initialise printer routines */
1667    fontsize_labels = 10;
1668    fontsize = 10;
1669
1670    colour_text = colour_labels = *wxBLACK;
1671
1672    wxColour colour_frame, colour_cross, colour_leg, colour_surface_leg;
1673    colour_frame = colour_cross = colour_leg = colour_surface_leg = *wxBLACK;
1674
1675    pen_frame = new wxPen(colour_frame);
1676    pen_cross = new wxPen(colour_cross);
1677    pen_leg = new wxPen(colour_leg);
1678    pen_surface_leg = new wxPen(colour_surface_leg);
1679    pen_splay = new wxPen(wxColour(128, 128, 128));
1680
1681    m_layout->scX = 1;
1682    m_layout->scY = 1;
1683
1684    font_labels = new wxFont(fontsize_labels, wxFONTFAMILY_DEFAULT,
1685                             wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
1686                             false, wxString(fontname_labels, wxConvUTF8),
1687                             wxFONTENCODING_ISO8859_1);
1688    font_default = new wxFont(fontsize, wxFONTFAMILY_DEFAULT,
1689                              wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
1690                              false, wxString(fontname, wxConvUTF8),
1691                              wxFONTENCODING_ISO8859_1);
1692}
1693
1694void
1695svxPrintout::OnEndPrinting() {
1696    delete font_labels;
1697    delete font_default;
1698    delete pen_frame;
1699    delete pen_cross;
1700    delete pen_leg;
1701    delete pen_surface_leg;
1702    delete pen_splay;
1703}
1704
1705int
1706svxPrintout::check_intersection(long x_p, long y_p)
1707{
1708#define U 1
1709#define D 2
1710#define L 4
1711#define R 8
1712   int mask_p = 0, mask_t = 0;
1713   if (x_p < 0)
1714      mask_p = L;
1715   else if (x_p > xpPageWidth)
1716      mask_p = R;
1717
1718   if (y_p < 0)
1719      mask_p |= D;
1720   else if (y_p > ypPageDepth)
1721      mask_p |= U;
1722
1723   if (x_t < 0)
1724      mask_t = L;
1725   else if (x_t > xpPageWidth)
1726      mask_t = R;
1727
1728   if (y_t < 0)
1729      mask_t |= D;
1730   else if (y_t > ypPageDepth)
1731      mask_t |= U;
1732
1733#if 0
1734   /* approximation to correct answer */
1735   return !(mask_t & mask_p);
1736#else
1737   /* One end of the line is on the page */
1738   if (!mask_t || !mask_p) return 1;
1739
1740   /* whole line is above, left, right, or below page */
1741   if (mask_t & mask_p) return 0;
1742
1743   if (mask_t == 0) mask_t = mask_p;
1744   if (mask_t & U) {
1745      double v = (double)(y_p - ypPageDepth) / (y_p - y_t);
1746      return v >= 0 && v <= 1;
1747   }
1748   if (mask_t & D) {
1749      double v = (double)y_p / (y_p - y_t);
1750      return v >= 0 && v <= 1;
1751   }
1752   if (mask_t & R) {
1753      double v = (double)(x_p - xpPageWidth) / (x_p - x_t);
1754      return v >= 0 && v <= 1;
1755   }
1756   wxASSERT(mask_t & L);
1757   {
1758      double v = (double)x_p / (x_p - x_t);
1759      return v >= 0 && v <= 1;
1760   }
1761#endif
1762#undef U
1763#undef D
1764#undef L
1765#undef R
1766}
1767
1768void
1769svxPrintout::MoveTo(long x, long y)
1770{
1771    x_t = x_offset + x - clip.x_min;
1772    y_t = y_offset + clip.y_max - y;
1773}
1774
1775void
1776svxPrintout::DrawTo(long x, long y)
1777{
1778    long x_p = x_t, y_p = y_t;
1779    x_t = x_offset + x - clip.x_min;
1780    y_t = y_offset + clip.y_max - y;
1781    if (!scan_for_blank_pages) {
1782        pdc->DrawLine(x_p, y_p, x_t, y_t);
1783    } else {
1784        if (check_intersection(x_p, y_p)) fBlankPage = false;
1785    }
1786}
1787
1788#define POINTS_PER_INCH 72.0
1789#define POINTS_PER_MM (POINTS_PER_INCH / MM_PER_INCH)
1790#define PWX_CROSS_SIZE (int)(2 * m_layout->scX / POINTS_PER_MM)
1791
1792void
1793svxPrintout::DrawCross(long x, long y)
1794{
1795   if (!scan_for_blank_pages) {
1796      MoveTo(x - PWX_CROSS_SIZE, y - PWX_CROSS_SIZE);
1797      DrawTo(x + PWX_CROSS_SIZE, y + PWX_CROSS_SIZE);
1798      MoveTo(x + PWX_CROSS_SIZE, y - PWX_CROSS_SIZE);
1799      DrawTo(x - PWX_CROSS_SIZE, y + PWX_CROSS_SIZE);
1800      MoveTo(x, y);
1801   } else {
1802      if ((x + PWX_CROSS_SIZE > clip.x_min &&
1803           x - PWX_CROSS_SIZE < clip.x_max) ||
1804          (y + PWX_CROSS_SIZE > clip.y_min &&
1805           y - PWX_CROSS_SIZE < clip.y_max)) {
1806         fBlankPage = false;
1807      }
1808   }
1809}
1810
1811void
1812svxPrintout::WriteString(const wxString & s)
1813{
1814    double xsc, ysc;
1815    pdc->GetUserScale(&xsc, &ysc);
1816    pdc->SetUserScale(xsc * font_scaling_x, ysc * font_scaling_y);
1817    if (!scan_for_blank_pages) {
1818        pdc->DrawText(s,
1819                      long(x_t / font_scaling_x),
1820                      long(y_t / font_scaling_y) - pdc->GetCharHeight());
1821    } else {
1822        int w, h;
1823        pdc->GetTextExtent(s, &w, &h);
1824        if ((y_t + h > 0 && y_t - h < clip.y_max - clip.y_min) ||
1825            (x_t < clip.x_max - clip.x_min && x_t + w > 0)) {
1826            fBlankPage = false;
1827        }
1828    }
1829    pdc->SetUserScale(xsc, ysc);
1830}
1831
1832void
1833svxPrintout::DrawEllipse(long x, long y, long r, long R)
1834{
1835    if (!scan_for_blank_pages) {
1836        x_t = x_offset + x - clip.x_min;
1837        y_t = y_offset + clip.y_max - y;
1838        const wxBrush & save_brush = pdc->GetBrush();
1839        pdc->SetBrush(*wxTRANSPARENT_BRUSH);
1840        pdc->DrawEllipse(x_t - r, y_t - R, 2 * r, 2 * R);
1841        pdc->SetBrush(save_brush);
1842    } else {
1843        /* No need to check - this is only used in the legend. */
1844    }
1845}
1846
1847void
1848svxPrintout::SolidRectangle(long x, long y, long w, long h)
1849{
1850    long X = x_offset + x - clip.x_min;
1851    long Y = y_offset + clip.y_max - y;
1852    pdc->SetBrush(*wxBLACK_BRUSH);
1853    pdc->DrawRectangle(X, Y - h, w, h);
1854}
1855
1856void
1857svxPrintout::NewPage(int pg, int pagesX, int pagesY)
1858{
1859    pdc->DestroyClippingRegion();
1860
1861    int x, y;
1862    x = (pg - 1) % pagesX;
1863    y = pagesY - 1 - ((pg - 1) / pagesX);
1864
1865    clip.x_min = (long)x * xpPageWidth;
1866    clip.y_min = (long)y * ypPageDepth;
1867    clip.x_max = clip.x_min + xpPageWidth; /* dm/pcl/ps had -1; */
1868    clip.y_max = clip.y_min + ypPageDepth; /* dm/pcl/ps had -1; */
1869
1870    const int FOOTERS = 4;
1871    wxString footer[FOOTERS];
1872    footer[0] = m_layout->title;
1873
1874    double rot = m_layout->rot;
1875    double tilt = m_layout->tilt;
1876    double scale = m_layout->Scale;
1877    switch (m_layout->view) {
1878        case layout::PLAN:
1879            // TRANSLATORS: Used in the footer of printouts to compactly
1880            // indicate this is a plan view and what the viewing angle is.
1881            // Aven will replace %s with the bearing, and %.0f with the scale.
1882            //
1883            // This message probably doesn't need translating for most languages.
1884            footer[1].Printf(wmsg(/*↑%s 1:%.0f*/233),
1885                    format_angle(ANGLE_FMT, rot).c_str(),
1886                    scale);
1887            break;
1888        case layout::ELEV:
1889            // TRANSLATORS: Used in the footer of printouts to compactly
1890            // indicate this is an elevation view and what the viewing angle
1891            // is.  Aven will replace the %s codes with the bearings to the
1892            // left and right of the viewer, and %.0f with the scale.
1893            //
1894            // This message probably doesn't need translating for most languages.
1895            footer[1].Printf(wmsg(/*%s↔%s 1:%.0f*/235),
1896                    format_angle(ANGLE_FMT, fmod(rot + 270.0, 360.0)).c_str(),
1897                    format_angle(ANGLE_FMT, fmod(rot + 90.0, 360.0)).c_str(),
1898                    scale);
1899            break;
1900        case layout::TILT:
1901            // TRANSLATORS: Used in the footer of printouts to compactly
1902            // indicate this is a tilted elevation view and what the viewing
1903            // angles are.  Aven will replace the %s codes with the bearings to
1904            // the left and right of the viewer and the angle the view is
1905            // tilted at, and %.0f with the scale.
1906            //
1907            // This message probably doesn't need translating for most languages.
1908            footer[1].Printf(wmsg(/*%s↔%s ∡%s 1:%.0f*/236),
1909                    format_angle(ANGLE_FMT, fmod(rot + 270.0, 360.0)).c_str(),
1910                    format_angle(ANGLE_FMT, fmod(rot + 90.0, 360.0)).c_str(),
1911                    format_angle(ANGLE2_FMT, tilt).c_str(),
1912                    scale);
1913            break;
1914        case layout::EXTELEV:
1915            // TRANSLATORS: Used in the footer of printouts to compactly
1916            // indicate this is an extended elevation view.  Aven will replace
1917            // %.0f with the scale.
1918            //
1919            // Try to keep the translation short (for example, in English we
1920            // use "Extended" not "Extended elevation") - there is limited room
1921            // in the footer, and the details there are mostly to make it easy
1922            // to check that you have corresponding pages from a multiple page
1923            // printout.
1924            footer[1].Printf(wmsg(/*Extended 1:%.0f*/244), scale);
1925            break;
1926    }
1927
1928    // TRANSLATORS: N/M meaning page N of M in the page footer of a printout.
1929    footer[2].Printf(wmsg(/*%d/%d*/232), pg, m_layout->pagesX * m_layout->pagesY);
1930
1931    wxString datestamp = m_layout->datestamp;
1932    if (!datestamp.empty()) {
1933        // Remove any timezone suffix (e.g. " UTC" or " +1200").
1934        wxChar ch = datestamp[datestamp.size() - 1];
1935        if (ch >= 'A' && ch <= 'Z') {
1936            for (size_t i = datestamp.size() - 1; i; --i) {
1937                ch = datestamp[i];
1938                if (ch < 'A' || ch > 'Z') {
1939                    if (ch == ' ') datestamp.resize(i);
1940                    break;
1941                }
1942            }
1943        } else if (ch >= '0' && ch <= '9') {
1944            for (size_t i = datestamp.size() - 1; i; --i) {
1945                ch = datestamp[i];
1946                if (ch < '0' || ch > '9') {
1947                    if ((ch == '-' || ch == '+') && datestamp[--i] == ' ')
1948                        datestamp.resize(i);
1949                    break;
1950                }
1951            }
1952        }
1953
1954        // Remove any day prefix (e.g. "Mon,").
1955        for (size_t i = 0; i != datestamp.size(); ++i) {
1956            if (datestamp[i] == ',' && i + 1 != datestamp.size()) {
1957                // Also skip a space after the comma.
1958                if (datestamp[i + 1] == ' ') ++i;
1959                datestamp.erase(0, i + 1);
1960                break;
1961            }
1962        }
1963    }
1964
1965    // TRANSLATORS: Used in the footer of printouts to compactly indicate that
1966    // the date which follows is the date that the survey data was processed.
1967    //
1968    // Aven will replace %s with a string giving the date and time (e.g.
1969    // "2015-06-09 12:40:44").
1970    footer[3].Printf(wmsg(/*Processed: %s*/167), datestamp.c_str());
1971
1972    const wxChar * footer_sep = wxT("    ");
1973    int fontsize_footer = fontsize_labels;
1974    wxFont * font_footer;
1975    font_footer = new wxFont(fontsize_footer, wxFONTFAMILY_DEFAULT,
1976                             wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
1977                             false, wxString(fontname_labels, wxConvUTF8),
1978                             wxFONTENCODING_UTF8);
1979    font_footer->Scale(font_scaling_x);
1980    SetFont(font_footer);
1981    int w[FOOTERS], ws, h;
1982    pdc->GetTextExtent(footer_sep, &ws, &h);
1983    int wtotal = ws * (FOOTERS - 1);
1984    for (int i = 0; i < FOOTERS; ++i) {
1985        pdc->GetTextExtent(footer[i], &w[i], &h);
1986        wtotal += w[i];
1987    }
1988
1989    long X = x_offset;
1990    long Y = y_offset + ypPageDepth + (long)(7 * m_layout->scY) - pdc->GetCharHeight();
1991
1992    if (wtotal > xpPageWidth) {
1993        // Rescale the footer so it fits.
1994        double rescale = double(wtotal) / xpPageWidth;
1995        double xsc, ysc;
1996        pdc->GetUserScale(&xsc, &ysc);
1997        pdc->SetUserScale(xsc / rescale, ysc / rescale);
1998        SetFont(font_footer);
1999        wxString fullfooter = footer[0];
2000        for (int i = 1; i < FOOTERS - 1; ++i) {
2001            fullfooter += footer_sep;
2002            fullfooter += footer[i];
2003        }
2004        pdc->DrawText(fullfooter, X * rescale, Y * rescale);
2005        // Draw final item right aligned to avoid misaligning.
2006        wxRect rect(x_offset * rescale, Y * rescale,
2007                    xpPageWidth * rescale, pdc->GetCharHeight() * rescale);
2008        pdc->DrawLabel(footer[FOOTERS - 1], rect, wxALIGN_RIGHT|wxALIGN_TOP);
2009        pdc->SetUserScale(xsc, ysc);
2010    } else {
2011        // Space out the elements of the footer to fill the line.
2012        double extra = double(xpPageWidth - wtotal) / (FOOTERS - 1);
2013        for (int i = 0; i < FOOTERS - 1; ++i) {
2014            pdc->DrawText(footer[i], X + extra * i, Y);
2015            X += ws + w[i];
2016        }
2017        // Draw final item right aligned to avoid misaligning.
2018        wxRect rect(x_offset, Y, xpPageWidth, pdc->GetCharHeight());
2019        pdc->DrawLabel(footer[FOOTERS - 1], rect, wxALIGN_RIGHT|wxALIGN_TOP);
2020    }
2021    drawticks((int)(9 * m_layout->scX / POINTS_PER_MM), x, y);
2022}
2023
2024void
2025svxPrintout::PlotLR(const vector<XSect> & centreline)
2026{
2027    const SurveyFilter* filter = mainfrm->GetTreeFilter();
2028    assert(centreline.size() > 1);
2029    const XSect* prev_pt_v = NULL;
2030    Vector3 last_right(1.0, 0.0, 0.0);
2031
2032    const double Sc = 1000 / m_layout->Scale;
2033    const double SIN = sin(rad(m_layout->rot));
2034    const double COS = cos(rad(m_layout->rot));
2035
2036    vector<XSect>::const_iterator i = centreline.begin();
2037    vector<XSect>::size_type segment = 0;
2038    while (i != centreline.end()) {
2039        // get the coordinates of this vertex
2040        const XSect & pt_v = *i++;
2041
2042        Vector3 right;
2043
2044        const Vector3 up_v(0.0, 0.0, 1.0);
2045
2046        if (segment == 0) {
2047            assert(i != centreline.end());
2048            // first segment
2049
2050            // get the coordinates of the next vertex
2051            const XSect & next_pt_v = *i;
2052
2053            // calculate vector from this pt to the next one
2054            Vector3 leg_v = next_pt_v - pt_v;
2055
2056            // obtain a vector in the LRUD plane
2057            right = leg_v * up_v;
2058            if (right.magnitude() == 0) {
2059                right = last_right;
2060            } else {
2061                last_right = right;
2062            }
2063        } else if (segment + 1 == centreline.size()) {
2064            // last segment
2065
2066            // Calculate vector from the previous pt to this one.
2067            Vector3 leg_v = pt_v - *prev_pt_v;
2068
2069            // Obtain a horizontal vector in the LRUD plane.
2070            right = leg_v * up_v;
2071            if (right.magnitude() == 0) {
2072                right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
2073            } else {
2074                last_right = right;
2075            }
2076        } else {
2077            assert(i != centreline.end());
2078            // Intermediate segment.
2079
2080            // Get the coordinates of the next vertex.
2081            const XSect & next_pt_v = *i;
2082
2083            // Calculate vectors from this vertex to the
2084            // next vertex, and from the previous vertex to
2085            // this one.
2086            Vector3 leg1_v = pt_v - *prev_pt_v;
2087            Vector3 leg2_v = next_pt_v - pt_v;
2088
2089            // Obtain horizontal vectors perpendicular to
2090            // both legs, then normalise and average to get
2091            // a horizontal bisector.
2092            Vector3 r1 = leg1_v * up_v;
2093            Vector3 r2 = leg2_v * up_v;
2094            r1.normalise();
2095            r2.normalise();
2096            right = r1 + r2;
2097            if (right.magnitude() == 0) {
2098                // This is the "mid-pitch" case...
2099                right = last_right;
2100            }
2101            last_right = right;
2102        }
2103
2104        // Scale to unit vectors in the LRUD plane.
2105        right.normalise();
2106
2107        double l = pt_v.GetL();
2108        double r = pt_v.GetR();
2109
2110        if (l >= 0 || r >= 0) {
2111            if (!filter || filter->CheckVisible(pt_v.GetLabel())) {
2112                // Get the x and y coordinates of the survey station
2113                double pt_X = pt_v.GetX() * COS - pt_v.GetY() * SIN;
2114                double pt_Y = pt_v.GetX() * SIN + pt_v.GetY() * COS;
2115                long pt_x = (long)((pt_X * Sc + m_layout->xOrg) * m_layout->scX);
2116                long pt_y = (long)((pt_Y * Sc + m_layout->yOrg) * m_layout->scY);
2117
2118                // Calculate dimensions for the right arrow
2119                double COSR = right.GetX();
2120                double SINR = right.GetY();
2121                long CROSS_MAJOR = (COSR + SINR) * PWX_CROSS_SIZE;
2122                long CROSS_MINOR = (COSR - SINR) * PWX_CROSS_SIZE;
2123
2124                if (l >= 0) {
2125                    // Get the x and y coordinates of the end of the left arrow
2126                    Vector3 p = pt_v.GetPoint() - right * l;
2127                    double X = p.GetX() * COS - p.GetY() * SIN;
2128                    double Y = (p.GetX() * SIN + p.GetY() * COS);
2129                    long x = (long)((X * Sc + m_layout->xOrg) * m_layout->scX);
2130                    long y = (long)((Y * Sc + m_layout->yOrg) * m_layout->scY);
2131
2132                    // Draw the arrow stem
2133                    MoveTo(pt_x, pt_y);
2134                    DrawTo(x, y);
2135
2136                    // Rotate the arrow by the page rotation
2137                    long dx1 = (+CROSS_MINOR) * COS - (+CROSS_MAJOR) * SIN;
2138                    long dy1 = (+CROSS_MINOR) * SIN + (+CROSS_MAJOR) * COS;
2139                    long dx2 = (+CROSS_MAJOR) * COS - (-CROSS_MINOR) * SIN;
2140                    long dy2 = (+CROSS_MAJOR) * SIN + (-CROSS_MINOR) * COS;
2141
2142                    // Draw the arrow
2143                    MoveTo(x + dx1, y + dy1);
2144                    DrawTo(x, y);
2145                    DrawTo(x + dx2, y + dy2);
2146                }
2147
2148                if (r >= 0) {
2149                    // Get the x and y coordinates of the end of the right arrow
2150                    Vector3 p = pt_v.GetPoint() + right * r;
2151                    double X = p.GetX() * COS - p.GetY() * SIN;
2152                    double Y = (p.GetX() * SIN + p.GetY() * COS);
2153                    long x = (long)((X * Sc + m_layout->xOrg) * m_layout->scX);
2154                    long y = (long)((Y * Sc + m_layout->yOrg) * m_layout->scY);
2155
2156                    // Draw the arrow stem
2157                    MoveTo(pt_x, pt_y);
2158                    DrawTo(x, y);
2159
2160                    // Rotate the arrow by the page rotation
2161                    long dx1 = (-CROSS_MINOR) * COS - (-CROSS_MAJOR) * SIN;
2162                    long dy1 = (-CROSS_MINOR) * SIN + (-CROSS_MAJOR) * COS;
2163                    long dx2 = (-CROSS_MAJOR) * COS - (+CROSS_MINOR) * SIN;
2164                    long dy2 = (-CROSS_MAJOR) * SIN + (+CROSS_MINOR) * COS;
2165
2166                    // Draw the arrow
2167                    MoveTo(x + dx1, y + dy1);
2168                    DrawTo(x, y);
2169                    DrawTo(x + dx2, y + dy2);
2170                }
2171            }
2172        }
2173
2174        prev_pt_v = &pt_v;
2175
2176        ++segment;
2177    }
2178}
2179
2180void
2181svxPrintout::PlotUD(const vector<XSect> & centreline)
2182{
2183    const SurveyFilter* filter = mainfrm->GetTreeFilter();
2184    assert(centreline.size() > 1);
2185    const double Sc = 1000 / m_layout->Scale;
2186
2187    vector<XSect>::const_iterator i = centreline.begin();
2188    while (i != centreline.end()) {
2189        // get the coordinates of this vertex
2190        const XSect & pt_v = *i++;
2191
2192        double u = pt_v.GetU();
2193        double d = pt_v.GetD();
2194
2195        if (u >= 0 || d >= 0) {
2196            if (filter && !filter->CheckVisible(pt_v.GetLabel()))
2197                continue;
2198
2199            // Get the coordinates of the survey point
2200            Vector3 p = pt_v.GetPoint();
2201            double SIN = sin(rad(m_layout->rot));
2202            double COS = cos(rad(m_layout->rot));
2203            double X = p.GetX() * COS - p.GetY() * SIN;
2204            double Y = p.GetZ();
2205            long x = (long)((X * Sc + m_layout->xOrg) * m_layout->scX);
2206            long pt_y = (long)((Y * Sc + m_layout->yOrg) * m_layout->scX);
2207
2208            if (u >= 0) {
2209                // Get the y coordinate of the up arrow
2210                long y = (long)(((Y + u) * Sc + m_layout->yOrg) * m_layout->scY);
2211
2212                // Draw the arrow stem
2213                MoveTo(x, pt_y);
2214                DrawTo(x, y);
2215
2216                // Draw the up arrow
2217                MoveTo(x - PWX_CROSS_SIZE, y - PWX_CROSS_SIZE);
2218                DrawTo(x, y);
2219                DrawTo(x + PWX_CROSS_SIZE, y - PWX_CROSS_SIZE);
2220            }
2221
2222            if (d >= 0) {
2223                // Get the y coordinate of the down arrow
2224                long y = (long)(((Y - d) * Sc + m_layout->yOrg) * m_layout->scY);
2225
2226                // Draw the arrow stem
2227                MoveTo(x, pt_y);
2228                DrawTo(x, y);
2229
2230                // Draw the down arrow
2231                MoveTo(x - PWX_CROSS_SIZE, y + PWX_CROSS_SIZE);
2232                DrawTo(x, y);
2233                DrawTo(x + PWX_CROSS_SIZE, y + PWX_CROSS_SIZE);
2234            }
2235        }
2236    }
2237}
Note: See TracBrowser for help on using the repository browser.