source: git/src/printing.cc

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

Don't check HAVE_CONFIG_H in most cases

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

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