source: git/src/printing.cc @ 8a7804fb

Last change on this file since 8a7804fb was 0b99107, checked in by Olly Betts <olly@…>, 2 weeks ago

Eliminate old FSF addresses

Update GPL/LGPL licence files and boilerplate to direct people who
didn't receive the licence text to the FSF website, as the current
versions of the FSF licence texts now do, rather than giving a postal
address.

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