source: git/src/printing.cc @ 8553bdb

RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernlogwalls-datawalls-data-hanging-as-warning
Last change on this file since 8553bdb was 32a040e, checked in by Olly Betts <olly@…>, 6 years ago

Support "clamp to ground" for KML export

The default altitude mode for KML is "clampToGround", which renders
data on the surface of the terrain. This is useful with KML viewers
which render the terrain as opaque so underground data isn't visible.
Rendering cave passages on the surface isn't great, but is better
than not being able to see them at all.

This option may also be helpful if you want to see where to look on the
surface for new entrances.

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