1%       $Id: pp3.w,v 1.42 2004/08/14 13:46:22 bronger Exp $
2
3@q======================================================================@>
4@q    pp3.w - Star Catalog Chart Creator                                @>
5@q    Copyright 2002--2004 Torsten Bronger                              @>
6@q                         <bronger@@users.sourceforge.net>             @>
7@q                                                                      @>
8@q  This program may be distributed and/or modified under the           @>
9@q  conditions of the MIT licence with the following contraint:         @>
10@q  If you copy or distribute a modified version of this Software, the  @>
11@q  entire resulting derived work must be given a different name and    @>
12@q  distributed under the terms of a permission notice identical to     @>
13@q  this one.                                                           @>
14@q                                                                      @>
15@q  For the full licence see below under "*LICENCE*" or the printed     @>
16@q  version of this program.                                            @>
17@q                                                                      @>
18@q  To get a compilable C++ source code, use                            @>
19@q       ctangle pp3.w - pp3.cc                                         @>
20@q  To get a structured documentation of the program, use               @>
21@q       cweave pp3.w                                                   @>
22@q       tex pp3                                                        @>
23@q                                                                      @>
24@q  cweave and ctangle belong to the cweb system by Levy/Knuth.         @>
25@q  Further information can be obtained at                              @>
26@q       http://www-cs-faculty.stanford.edu/~knuth/cweb.html            @>
27@q======================================================================@>
28
29\catcode`@@=11
30
31% First the default font setup
32
33\font\ninett=cmtt9
34\font\nineit=cmti9
35
36\font\stitlefont=cmss10 scaled\magstep3      % sans serif type in title
37\font\sbtitlefont=cmssbx10 scaled\magstep3   % sans bold type in title
38\font\sf=cmss10 \font\sfbf=cmssbx10
39\font\sfa=cmss7
40
41%% From here with Palatino
42\font\tenrm=pplr7t % roman text
43\font\ninerm=pplr7t scaled 900
44\font\eightrm=pplr7t scaled 800
45\font\sevenrm=pplr7t scaled 700
46\font\fiverm=pplr7t scaled 500
47
48\font\teni=zplmr7m % math italic    (zptmcm7m for Times, zplmr7m for Palatino)
49\font\seveni=zplmr7m scaled 700
50\font\fivei=zplmr7m scaled 500
51
52\font\tensy=zplmr7y % math symbols  (zptmcm7y for Times, zplmr7y for Palatino)
53\font\sevensy=zplmr7y scaled 700
54\font\fivesy=zplmr7y scaled 500
55
56\font\tenex=zplmr7v % math extension (zptmcm7v for Times, zplmr7v f. Palatino)
57
58\font\tenbf=pplb7t % boldface extended
59\font\sevenbf=pplb7t scaled 700
60\font\fivebf=pplb7t scaled 500
61
62\font\tentt=cmtt10 % typewriter
63\font\ninett=cmtt9 % typewriter
64
65\font\tensl=pplro7t % slanted roman
66
67\font\tenit=pplri7t % text italic
68
69\skewchar\teni='177 \skewchar\seveni='177 \skewchar\fivei='177
70\skewchar\tensy='60 \skewchar\sevensy='60 \skewchar\fivesy='60
71
72\textfont0=\tenrm \scriptfont0=\sevenrm \scriptscriptfont0=\fiverm
73\def\rm{\fam\z@@\tenrm}
74\textfont1=\teni \scriptfont1=\seveni \scriptscriptfont1=\fivei
75\def\mit{\fam\@@ne} \def\oldstyle{\fam\@@ne\teni}
76\textfont2=\tensy \scriptfont2=\sevensy \scriptscriptfont2=\fivesy
77\def\cal{\fam\tw@@}
78\textfont3=\tenex \scriptfont3=\tenex \scriptscriptfont3=\tenex
79\newfam\itfam \def\it{\fam\itfam\tenit} % \it is family 4
80\textfont\itfam=\tenit
81\newfam\slfam \def\sl{\fam\slfam\tensl} % \sl is family 5
82\textfont\slfam=\tensl
83\newfam\bffam \def\bf{\fam\bffam\tenbf} % \bf is family 6
84\textfont\bffam=\tenbf \scriptfont\bffam=\sevenbf
85\scriptscriptfont\bffam=\fivebf
86\newfam\ttfam \def\tt{\fam\ttfam\tentt} % \tt is family 7
87\textfont\ttfam=\tentt
88
89\catcode`@@=12
90
91\font\eightpplr=pplr7t scaled 800
92\font\ninepplr=pplr7t scaled 900
93\font\ninepplri=pplri7t scaled 900
94\font\tenpplr=pplr7t
95\font\tenpplb=pplb7t
96\font\tenpplri=pplri7t
97\font\titlefont=pplr7t scaled 1728 % title on the contents page
98\font\ttitlefont=cmtt10 scaled\magstep2 % typewriter type in title
99\font\tentex=cmtex10 % TeX extended character set (used in strings)
100\fontdimen7\tentex=0pt % no double space after sentences
101
102\let\mainfont=\tenpplr
103\let\sc=\ninepplr
104\let\mc=\ninepplr
105\let\it=\tenpplri
106\let\nineit=\ninepplri
107\let\tt=\tentt
108\let\cmntfont\tenpplr
109
110\mainfont\baselineskip12.7pt
111
112%% Now for the title page
113
114\font\sf=bfrr8t \font\sfbf=bfrb8t
115\font\sfa=bfrr8t scaled 700
116
117\font\stitlefont=phvr7t scaled\magstep3    % sans serif type in title
118\font\sbtitlefont=phvb7t scaled\magstep3   % sans bold type in title
119\font\ttitlefont=pcrb7t scaled\magstep3    % typewriter type in title
120
121\font\stitlefont=bfrr8t scaled\magstep3    % sans serif type in title
122\font\sbtitlefont=bfrb8t scaled\magstep3   % sans serif type in title
123%\font\sbtitlefont=0t3x8r scaled\magstep3   % sans bold type in title
124%\font\sf=0t3r8r \font\sfbf=0t3b8r
125%\font\sfa=0t3r8r scaled 700
126
127
128\hyphenation{white-space}
129
130\secpagedepth=1
131\def\TeX{T\kern-.1667em\lower.5ex\hbox{E}\kern-.125emX}
132\def\LaTeX{L\kern-.36em%
133        {\setbox0=\hbox{T}%
134         \vbox to\ht0{\hbox{\sevenrm A}\vss}%
135        }%
136        \kern-.15em%
137        \TeX}
138\def\LaTeXbf{L\kern-.36em%
139        {\setbox0=\hbox{T}%
140         \vbox to\ht0{\hbox{\sevenbf A}\vss}%
141        }%
142        \kern-.15em%
143        \TeX}
144\def\AMSinner{\cal A\mkern-2mu\raise-0.5ex\hbox{$\cal M$}\mkern-2muS}
145\def\AMS{\ifmmode\AMSinner\else$\AMSinner$\fi}
146\def\BSC/{{\mc BSC\spacefactor1000}}
147\def\HSB/{{\mc HSB\spacefactor1000}}
148\def\EPS/{{\mc EPS\spacefactor1000}}
149\def\PS/{{\mc PS\spacefactor1000}}
150\def\PDF/{{\mc PDF\spacefactor1000}}
151\def\RGB/{{\mc RGB\spacefactor1000}}
152\def\NGC/{{\mc NGC\spacefactor1000}}
153\def\IC/{{\mc IC\spacefactor1000}}
154\def\HD/{{\mc HD\spacefactor1000}}
155
156\def\9#1{}
157
158\def\sloppy{\tolerance9999\emergencystretch3em\hfuzz .5pt\vfuzz\hfuzz}
159
160\def\title{PP3 (Version 1.3)}
161\def\topofcontents{\null\vfill\vskip-1.5cm
162  \centerline{\titlefont The Sky Map Creator
163               {\sbtitlefont PP\lower.35ex\hbox{3}}}
164  \vskip 15pt
165  \centerline{(Version 1.3)}
166  \vfill}
167\def\botofcontents{\parindent2em\vfill\vfill\vfill\vfill\vfill
168\noindent
169%
170%                                *LICENCE*
171%
172%
173Copyright \copyright\ 2002--2004 Torsten Bronger
174                           ({\tt bronger@@users.sourceforge.net})
175\ninerm\baselineskip0.9\baselineskip\let\it\nineit
176\bigskip\noindent
177Permission is hereby granted, free of charge, to any person obtaining a copy of
178this software and associated documentation files (the ``Software''), to deal in
179the Software without restriction, including without limitation the rights to
180use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
181of the Software, and to permit persons to whom the Software is furnished to do
182so, subject to the following conditions:
183
184\leftskip1cm\rightskip\leftskip
185\medskip\noindent
186The above copyright notice and this permission notice shall be included in all
187copies or substantial portions of the Software.
188
189If you copy or distribute a modified version of this Software, the entire
190resulting derived work must be given a different name and distributed under the
191terms of a permission notice identical to this one.
192
193\leftskip0cm\rightskip\leftskip\bigskip\noindent
194The Software is provided ``as~is'', without warranty of any kind, express or
195implied, including but not limited to the warranties of {\it merchantability},
196{\it fitness for a particular purpose\/} and {\it noninfringement}.  In no
197event shall the authors or copyright holders be liable for any claim, damages
198or other liability, whether in an action of contract, tort or otherwise,
199arising from, out of or in connection with the Software or the use or other
200dealings in the Software.\vskip-1cm}
201
202\def\PPTHREE/{{\sf PP\lower.35ex\hbox{3}}}
203
204@i c++lib.w
205@s ios int
206
207@** Introduction.  \ifpdftex (Please note that you can find a table of contents
208at the end of this document). \fi This program \PPTHREE/ (``parvum
209planetarium'') takes the data of various celestial data files and turns them
210into a \LaTeX\ file that uses \PS/Tricks to draw a nice sky chart containing a
211certain region of the sky.  Current versions are available on its
212\pdfURL{homepage}{http://pp3.sourceforge.net}.
213
214You call \PPTHREE/ with e.\,g.\medskip
215
216\.{pp3 mychart.pp3}
217
218\medskip\noindent The data files (\.{stars.dat}, \.{nebulae.dat},
219\.{boundaries.dat}, \.{labeldimens.dat}, \.{lines.dat}, and
220\.{milkyway}\hskip0pt\.{.dat}) must be in the proper directory.  The proper
221directory was compiled into \PPTHREE/.  With Linux, normally it's
222\.{/usr/local/share/pp3/}, with Windows the current directory simply.  But the
223environment variable \.{PP3DATA} can override that.
224
225The resulting chart is by default sent to standard output which you may
226redirect into a file.  But you can define an output filename explicitly in the
227input script.
228
229If you want to use other data with this program, you may well provide your own
230catalogue files.  Their file formats are very simple, and they are explained in
231this document together with the respective |read_@t\dots@>()| function.
232
233If you give a single dash ``\.{-}'' as the only parameter to \PPTHREE/, the
234input script is read from standard input.  So if you write\medskip
235
236\.{pp3 - > test.tex \AM\AM{} latex test \AM\AM{} dvips test}
237
238\medskip\noindent and type \.{\CF D} (\hbox{Control-D})\footnote{$^1$}{it's
239Control-Z-Return on Windows}, a file \.{test.ps} should be produced that
240contains a sample chart.
241
242Very important is to know how to write an input script.  Please consult the
243following section ``The input script'' for this.  Here is an example input
244script: \medskip
245
246{\parindent2em\baselineskip10.5pt\ninett\obeylines\obeyspaces
247\# Chart of the Scorpion, printable on a
248\# black and white printing device
249\vskip\baselineskip
250set constellation SCO           \# This is highlighted
251set center\UL{}rectascension   17
252set center\UL{}declination    -28
253set grad\UL{}per\UL{}cm             2.5
254\vskip\baselineskip
255switch milky\UL{}way on
256switch eps\UL{}output on            \# Please call LaTeX and dvips for us
257switch colored\UL{}stars off        \# All the same colour ...
258color stars 0 0 0               \# ...\ namely this one
259color nebulae 0 0 0
260color background 1 1 1
261color grid 0.5 0.5 0.5
262color ecliptic 0.3 0.3 0.3
263color constellation\UL{}lines 0.7 0.7 0.7
264color labels 0 0 0
265color boundaries 0.8 0.8 0.8
266color highlighted\UL{}boundaries 0 0 0
267color milky\UL{}way 0.5 0.5 0.5
268\vskip\baselineskip
269filename output test.tex        \# Here should it go
270\vskip\baselineskip
271\vskip\baselineskip
272objects\UL{}and\UL{}labels              \# Now for the second part
273\vskip\baselineskip
274delete M 18  NGC 6590  NGC 6634 ;  \# Delete superfluous
275reposition SCO 20 S ;           \# Force sig Sco to be labelled.
276text "\BS\BS{}Huge Sco" at 16.2 -41.5 color 0 0 0 towards NW ;
277}
278
279@ The resulting \LaTeX\ file doesn't use packages special to \PPTHREE/.  In
280fact the preamble is rather small.  This makes it possible to copy the (maybe
281huge) \.{\BS vbox} with the complete map into an own \LaTeX\ file.  However
282this may be a stupid decision because (especially if the Milky Way is switched
283on) this consumes much of \TeX's pool size.
284
285Some \PPTHREE/ figures will need a lot of \TeX's memory.  Normally this is
286problematic only if the Milky Way is included.  If you show a too large portion
287of the sky, and a lot of Milky Way area is involved, it may even happen that
288\LaTeX\ simply cannot process it.  You should use Milky Way data with lower
289resolution in this case.
290
291Make sure that the \PS/Tricks package is properly installed.
292
293Please note that you cannot use pdf\/\LaTeX\ with \PPTHREE/, because \PS/Tricks
294can't be used with pdf\/\LaTeX\spacefactor1000.  This is not a problem.  First,
295you can convert all \EPS/ files easily to \PDF/ by hand, and secondly \PPTHREE/
296can even call \PS/2\PDF/ for you.
297
298@ In order to use the program, you must have a complete and modern \LaTeX\
299distribution installed, and a modern Ghostscript.  On a Linux system, both
300programs are possibly already available, and if not you may install them with a
301package management program.
302
303{\sloppy On Windows, you will probably have to install them by hand.  You can
304download the \pdfURL{Mik\TeX\ distribution}{http://www.miktex.org} or get the
305\pdfURL{\TeX{}Live~{\mc CD}}{http://www.tug.org/texlive/}\spacefactor1000.  If
306you install
307\pdfURL{Ghostscript}{http://www.cs.wisc.edu/\TILDE/ghost/doc/AFPL/get811.htm},
308notice that \pdfURL{{\mc
309GS}View}{http://www.cs.wisc.edu/\TILDE/ghost/gsview/get45.htm} is a very
310sensible program, too.\par}
311
312@ Some flaws and bugs in this program are already known to its author.
313
314The input script processing is shaky.  The comment character `\.{\#}' must be
315at the beginning of a line or must be preceded by whitespace.  The special
316token ``\.{objects\UL and\UL labels}'' must not occur within strings.  If an
317error is found in the input script, \PPTHREE/ doesn't tell the line number.  It
318should be possible to include more than one file, and it should allow for a
319nesting depth greater than one.
320
321At the moment almost all data structures are kept in memory completely.  For
322the author's needs this is perfectly sufficient, however if you want to use
323data bases with hundreds of thousands of objects, you will run into trouble.
324On the other hand it's only necessary to keep all object in memory that are
325actually drawn.  So memory usage could be reduced drastically.
326
327@ Okay, let's start with the header files~\dots
328
329@q'}}}}@>
330
331@c
332#include <iostream>
333#include <string>
334#include <fstream>
335#include <sstream>
336#include <vector>
337#include <map>
338#include <list>
339#include <iomanip>
340#include <cstdlib>
341#include <cmath>
342#include <cfloat>@#
343
344using namespace std;
345
346@<Missing math routines@>@;@#
347
348@* Global parameters and default values.  I have to break a strict \CPLUSPLUS/
349rule here: Never use \hbox{|#@t\hskip-\fontdimen2\mainfont@>define|}!  However
350I really found no alternative to~|OUT|.  No |const| construct worked, and if it
351had done, I'd have to use it in every single routine.  And ubiquitous
352|*params.out|'s are ugly.
353
354@d OUT (*params.out)
355
356
357@ The following makes it possible to compile a directory prefix into \PPTHREE/
358for the data files.  By default, the data files must be in the current
359directory.  You may say e.\,g.\ $$\hbox{\.{g++
360-DPP3DATA=\BS"/usr/share/pp3/\BS" -O2 -s pp3.cc -o pp3}}$$ then they are
361searched in \.{/usr/share/pp3/}.  If set, an environment variable called
362\.{PP3DATA} has highest priority though.
363
364\def\NULL{0}
365
366@s NULL TeX
367
368@c
369const char* pp3data_env_raw = getenv("PP3DATA");
370const string pp3data_env = (pp3data_env_raw == NULL ?
371                            "" : pp3data_env_raw);
372#ifdef PP3DATA
373const string pp3data(PP3DATA);
374#else
375const string pp3data;
376#endif
377const string filename_prefix(!pp3data_env.empty() ?
378                             pp3data_env + '/' : (pp3data.empty() ?
379                                                  "" : pp3data + "/"));
380
381
382@ I declare {\it and\/} define the structure |parameters| here.
383
384@c
385@<Define |color| data structure@>@;@#
386
387struct parameters {
388    double center_rectascension, center_declination;
389    double view_frame_width, view_frame_height;
390    double grad_per_cm;
391    double label_skip, minimal_nebula_radius, faintest_cluster_magnitude,
392        faintest_diffuse_nebula_magnitude, faintest_star_magnitude,
393        star_scaling, minimal_star_radius, faintest_star_disk_magnitude,
394        faintest_star_with_label_magnitude, shortest_constellation_line;
395    string constellation;
396    int font_size;
397    double penalties_label, penalties_star, penalties_nebula,
398        penalties_boundary, penalties_boundary_rim, penalties_cline,
399        penalties_cline_rim, penalties_threshold, penalties_rim;
400    string filename_stars, filename_nebulae, filename_dimensions,
401        filename_lines, filename_boundaries, filename_milkyway,
402        filename_preamble, filename_include;
403    string filename_output;
404    ostream* out;
405    istream* in;
406    bool input_file;
407    color bgcolor, gridcolor, eclipticcolor, boundarycolor, hlboundarycolor,
408        starcolor, nebulacolor, labelcolor, textlabelcolor, clinecolor,
409        milkywaycolor;
410    double linewidth_grid, linewidth_ecliptic, linewidth_boundary,
411        linewidth_hlboundary, linewidth_cline, linewidth_nebula;
412    string linestyle_grid, linestyle_ecliptic, linestyle_boundary,
413        linestyle_hlboundary, linestyle_cline, linestyle_nebula;
414    bool milkyway_visible, nebulae_visible, colored_stars, show_grid,
415        show_ecliptic, show_boundaries, show_lines, show_labels;
416    bool create_eps, create_pdf;
417    parameters() : @<Default values of all global parameters@> { }
418    int view_frame_width_in_bp() {
419        return int(ceil(view_frame_width / 2.54 * 72));
420    }
421    int view_frame_height_in_bp() {
422        return int(ceil(view_frame_height / 2.54 * 72));
423    }
424} params;
425
426@ @<Default values of all global parameters@>=
427center_rectascension(5.8), center_declination(0.0),
428                   view_frame_width(15.0), view_frame_height(15.0),
429                   grad_per_cm(4.0),
430                   constellation("ORI"), font_size(10), @/
431                   label_skip(0.06), minimal_nebula_radius(0.1),
432                   faintest_cluster_magnitude(4.0),
433                   faintest_diffuse_nebula_magnitude(8.0),
434                   faintest_star_magnitude(7.0), star_scaling(1.0),
435                   minimal_star_radius(0.015),
436                   faintest_star_disk_magnitude(4.5),
437                   faintest_star_with_label_magnitude(3.7),
438                   shortest_constellation_line(0.1), @/
439                   penalties_label(1.0), penalties_star(1.0),
440                   penalties_nebula(1.0), penalties_boundary(1.0),
441                   penalties_boundary_rim(1.0), penalties_cline(1.0),
442                   penalties_cline_rim(1.0), penalties_threshold(1.0),
443                   penalties_rim(1.0), @/
444                   filename_stars(filename_prefix + "stars.dat"),
445                   filename_nebulae(filename_prefix + "nebulae.dat"),
446                   filename_dimensions("labeldimens.dat"),
447                   filename_lines(filename_prefix + "lines.dat"),
448                   filename_boundaries(filename_prefix + "boundaries.dat"),
449                   filename_milkyway(filename_prefix + "milkyway.dat"), @/
450                   filename_preamble(), filename_include(),
451                   filename_output(), out(&cout), in(0), input_file(false), @/
452                   bgcolor("bgcolor", 0, 0, 0.4),
453                   gridcolor("gridcolor", 0, 0.298, 0.447),
454                   eclipticcolor("eclipticcolor", 1, 0, 0),
455                   boundarycolor("boundarycolor", 0.5, 0.5, 0),
456                   hlboundarycolor("hlboundarycolor", 1, 1, 0),
457                   starcolor("starcolor", 1, 1, 1),
458                   nebulacolor("nebulacolor", 1, 1, 1),
459                   labelcolor("labelcolor", 0, 1, 1),
460                   textlabelcolor(1, 1, 0),
461                   clinecolor("clinecolor", 0, 1, 0),
462                   milkywaycolor(0, 0, 1), @/
463                   linewidth_grid(0.025), linewidth_ecliptic(0.018),
464                   linewidth_boundary(0.035), linewidth_hlboundary(0.035),
465                   linewidth_cline(0.035), linewidth_nebula(0.018), @/
466                   linestyle_grid("solid"), linestyle_ecliptic("dashed"),
467                   linestyle_boundary("dashed"),
468                   linestyle_hlboundary("dashed"), linestyle_cline("solid"),
469                   linestyle_nebula("solid"), @/
470                   milkyway_visible(false),
471                   nebulae_visible(true),
472                   colored_stars(true),
473                   show_grid(true), show_ecliptic(true), show_boundaries(true),
474                   show_lines(true), show_labels(true), @/
475                   create_eps(false),
476                   create_pdf(false)
477
478
479@** The input script.  The input script is a text file that is given as the
480first and only parameter to \PPTHREE/.  Its format is very simple: First, a
481`\.{\#}' introduces a comment and the rest of the line is ignored.  Secondly,
482every command has an opcode--parameter(s) form.  Thirdly, opcodes and
483parameters are separated by whitespace (no matter which type and how much).
484Forthly, parameters and celestial objects must be separated by
485``\.{objects\_and\_labels}''.
486
487@* Part~I: Global parameters.  Every input script can be divided into two
488parts, however the second may be absent.  They are separated from each other by
489the token ``\.{objects\_and\_labels}''.  Here we process the first part of the
490input script.
491
492First two small helping routines that just read simple values from the file.
493
494@q'@>
495
496@<Routines for reading the input script@>=
497bool read_boolean(istream& script) {
498    string boolean;
499    script >> boolean;
500    if (boolean == "on") return true;
501    else if (boolean == "off") return false;
502    else throw string("Expected \"on\" or \"off\" in \"switch\" construct "
503                      "in input script");
504}
505
506@ You can give strings in a similar way as on a shell command line:  If it
507doesn't contain spaces, just input it.  In the other case you have to enclose
508it within \.{"..."}.  The same is necessary if it starts with a~\.{"}.  Within
509the double braces, backslashes and double braces have to be escaped with a
510backslash.  This is not necessary if you had a simple string.
511
512So you may write e.\,g.: \.{Leo}, \.{"Leo Minor"}, \.{\BS alpha}, and
513\.{"\LB\BS\BS sfseries Leo\RB"}.  An empty string can only be written as
514\.{""}.
515
516@q'@>
517
518@<Routines for reading the input script@>=
519string read_string(istream& script) {
520    const string UnexpectedEOS("Unexpected end of input script while reading a"
521                               " string parameter");
522    char c;
523    string contents;
524    while (script.get(c)) if (!isspace(c)) break;
525    if (!script) throw UnexpectedEOS;
526    if (c != '"') {
527        script >> contents;
528        if (script) contents.insert(contents.begin(),c); else contents = c;
529    } else {
530        while (script.get(c)) {
531            if (c == '"') break;
532            if (c == '\\') {
533                if (!script.get(c))
534                    throw UnexpectedEOS;
535                switch (c) {
536                case '\\': case '"': contents += c; break;
537                default: throw string("Unknown escape sequence in string");
538                }
539            } else contents += c;
540        }
541        if (!script)
542            throw UnexpectedEOS;
543    }
544    return contents;
545}
546
547@ {\sloppy Here the actual routine for this first part.  The top-level keywords
548are: ``\.{color}'', ``\.{line\_width}'', ``\.{line\_style}'', ``\.{switch}'',
549``\.{penalties}'', ``\.{filename}'', and ``\.{set}''.\par}
550
551@.color@>
552@.line\_width@>
553@.line\_style@>
554@.switch@>
555@.penalties@>
556@.filename@>
557@.set@>
558
559@<Routines for reading the input script@>=
560void read_parameters_from_script(istream& script) {
561    string opcode;
562    script >> opcode;
563    while (opcode != "objects_and_labels" && script) {
564        if (opcode[0] == '#') {   // skip comment line
565            string rest_of_line;
566            getline(script,rest_of_line);
567        } else
568            @<Set color parameters@>@;
569        else
570            @<Set line widths@>@;
571        else
572            @<Set line styles@>@;
573        else
574            @<Set on/off parameters@>@;
575        else
576            @<Set penalty parameters@>@;
577        else
578            @<Set filename parameters@>@;
579        else
580            @<Set single value parameters@>@;
581        else throw string("Undefined opcode in input script: \"")
582                 + opcode + '"';
583        script >> opcode;
584    }
585}
586
587@ {\sloppy Colours are given as red--green--blue values from $0$ to~$1$.  For example,
588$$\hbox{\.{color labels 1 0 0}}$$ which makes all labels red.  The following
589sub-keywords can be used: ``\.{background}'', ``\.{grid}'',
590``\.{eclip}\-\.{tic}'', ``\.{boundaries}'', ``\.{highlighted\_boundaries}'',
591``\.{stars}'', ``\.{nebulae}'', ``\.{labels}'',
592``\.{text\_}\hskip0pt\.{labels}'', ``\.{constellation\_}\hskip0pt\.{lines}'',
593and ``\.{milky\_way}''.  In case of the milky way, the colour denotes the
594brightest regions.  (The darkest have \.{back}\-\.{ground} colour.)\par}
595
596@.color@>
597@.background@>
598@.grid@>
599@.ecliptic@>
600@.boundaries@>
601@.highlighted\_boundaries@>
602@.stars@>
603@.nebulae@>
604@.labels@>
605@.text\_labels@>
606@.constellation\_lines@>
607@.milky\_way@>
608
609@<Set color parameters@>=
610        if (opcode == "color") {
611            string color_name;
612            script >> color_name;
613            if (color_name == "background") script >> params.bgcolor;
614            else if (color_name == "grid") script >> params.gridcolor;
615            else if (color_name == "ecliptic") script >> params.eclipticcolor;
616            else if (color_name == "boundaries")
617                script >> params.boundarycolor;
618            else if (color_name == "highlighted_boundaries")
619                script >> params.hlboundarycolor;
620            else if (color_name == "stars") script >> params.starcolor;
621            else if (color_name == "nebulae") script >> params.nebulacolor;
622            else if (color_name == "labels") script >> params.labelcolor;
623            else if (color_name == "text_labels")
624                script >> params.textlabelcolor;
625            else if (color_name == "constellation_lines")
626                script >> params.clinecolor;
627            else if (color_name == "milky_way") script >> params.milkywaycolor;
628            else throw string("Undefined \"color\" construct"
629                              " in input script: \"") + color_name + '"';
630        }
631
632@ {\sloppy\raggedright The following lines can be modified: ``\.{grid}'',
633``\.{ecliptic}'', ``\.{boundaries}'',
634``\.{highlighted\_}\hskip0pt\.{boundaries}'', ``\.{nebulae}'', and
635``\.{constellation\_lines}''.  The linewidth in centimetres must follow.\par}
636
637@.line\_width@>
638@.grid@>
639@.ecliptic@>
640@.boundaries@>
641@.highlighted\_boundaries@>
642@.nebulae@>
643@.constellation\_lines@>
644
645@<Set line widths@>=
646        if (opcode == "line_width") {
647            string line_name;
648            script >> line_name;
649            if (line_name == "grid") script >> params.linewidth_grid;
650            else if (line_name == "ecliptic")
651                script >> params.linewidth_ecliptic;
652            else if (line_name == "boundaries")
653                script >> params.linewidth_boundary;
654            else if (line_name == "highlighted_boundaries")
655                script >> params.linewidth_hlboundary;
656            else if (line_name == "nebulae")
657                script >> params.linewidth_nebula;
658            else if (line_name == "constellation_lines")
659                script >> params.linewidth_cline;
660            else throw string("Undefined \"line_width\" construct"
661                              " in input script: \"") + line_name + '"';
662        }
663
664@ {\sloppy\raggedright The following lines can be modified: ``\.{grid}'',
665``\.{ecliptic}'', ``\.{boundaries}'',
666``\.{highlighted\_}\hskip0pt\.{boundaries}'', ``\.{nebulae}'', and
667``\.{constellation\_lines}''.  You can set the respective line style to
668``\.{solid}'', ``\.{dashed}'', and ``\.{dotted}''.\par}
669
670@.line\_style@>
671@.solid@>
672@.dashed@>
673@.dotted@>
674@.grid@>
675@.ecliptic@>
676@.boundaries@>
677@.highlighted\_boundaries@>
678@.nebulae@>
679@.constellation\_lines@>
680
681@<Set line styles@>=
682        if (opcode == "line_style") {
683            string line_name;
684            script >> line_name;
685            if (line_name == "grid") script >> params.linestyle_grid;
686            else if (line_name == "ecliptic")
687                script >> params.linestyle_ecliptic;
688            else if (line_name == "boundaries")
689                script >> params.linestyle_boundary;
690            else if (line_name == "highlighted_boundaries")
691                script >> params.linestyle_hlboundary;
692            else if (line_name == "nebulae")
693                script >> params.linestyle_nebula;
694            else if (line_name == "constellation_lines")
695                script >> params.linestyle_cline;
696            else throw string("Undefined \"line_width\" construct"
697                              " in input script: \"") + line_name + '"';
698        }
699
700
701@ {\sloppy There are the following boolean values: ``\.{milky\_may}'',
702``\.{nebulae}'', ``\.{colored\_stars}'', ``\.{grid}'', ``\.{ecliptic}'',
703``\.{boundaries}'', ``\.{constellation\_lines}'', ``\.{labels}'',
704``\.{eps\_output}'', and ``\.{pdf\_output}''.  You can switch them ``\.{on}''
705or ``\.{off}''.\par}
706
707@.switch@>
708@.on@>
709@.off@>
710@.milky\_way@>
711@.nebulae@>
712@.colored\_stars@>
713@.grid@>
714@.ecliptic@>
715@.boundaries@>
716@.constellation\_lines@>
717@.labels@>
718@.eps\_output@>
719@.pdf\_output@>
720
721@<Set on/off parameters@>=
722        if (opcode == "switch") {
723            string switch_name;
724            script >> switch_name;
725            if (switch_name == "milky_way")
726                params.milkyway_visible = read_boolean(script);
727            else if (switch_name == "nebulae")
728                params.nebulae_visible = read_boolean(script);
729            else if (switch_name == "colored_stars")
730                params.colored_stars = read_boolean(script);
731            else if (switch_name == "grid")
732                params.show_grid = read_boolean(script);
733            else if (switch_name == "ecliptic")
734                params.show_ecliptic = read_boolean(script);
735            else if (switch_name == "boundaries")
736                params.show_boundaries = read_boolean(script);
737            else if (switch_name == "constellation_lines")
738                params.show_lines = read_boolean(script);
739            else if (switch_name == "labels")
740                params.show_labels = read_boolean(script);
741            else if (switch_name == "eps_output")
742                params.create_eps = read_boolean(script);
743            else if (switch_name == "pdf_output")
744                params.create_pdf = read_boolean(script);
745            else throw string("Undefined \"switch\" construct"
746                              " in input script: \"") + switch_name + '"';
747        }
748
749@ In order to avoid overlaps, \PPTHREE/ uses a simple penalty algorithm.  The
750standard value for all penalty values is~1000.  The meanings of ``\.{stars}'',
751``\.{labels}'', ``\.{nebulae}'', ``\.{boundaries}'', and
752``\.{constellation\_lines}'' is pretty easy to explain: They come into play
753when the current label (that is to be positioned) overlaps with the respective
754object.  For example, if you want overlaps with constellation lines to be less
755probable, you can say $$\hbox{\.{penalties constellation\_lines 2000}}$$
756
757There is another concept of importance here: The rim.  A rim is a rectangular
758margin around every label with a width of |skip|.  Overlaps in the rim are
759counted, too, however normally they don't hurt that much.  Normally they hurt
760half as much as the label area ({\it core}) itself, but this can be changed
761with ``\.{rim}''.  With $$\hbox{\.{penalties rim 0}}$$ the rim loses its
762complete significance.  But notice that for each core penalty a rim penalty is
763added, too, so that the rim can never be more significant than the core.
764
765Within the rim, ``\.{boundaries\_rim}'' and ``\.{constellation\_lines\_rim}''
766are used instead of the normal ones.  This is because lines are not so bad in
767the rim as other stars or nebulae would be, because other stars in the vicinity
768of a label may cause confusion, lines not.
769
770\medskip The third thing about penalties is the maximal penalty of a label.  If
771the penalties of a label exceed this value, the label is supressed.  You may
772overrule this behaviour with an explicit repositioning of the label.  You can
773adjust this maximal badness of the label with ``\.{threshold}''.  With
774$$\hbox{\.{penalties threshold 10000}}$$ probably all labels are printed.
775
776@.penalties@>
777@.stars@>
778@.labels@>
779@.nebulae@>
780@.boundaries@>
781@.boundaries\_rim@>
782@.constellation\_lines@>
783@.constellation\_lines\_rim@>
784@.threshold@>
785@.rim@>
786
787@q'@>
788
789@<Set penalty parameters@>=
790        if (opcode == "penalties") {
791            string penalty_name;
792            double value;
793            script >> penalty_name >> value;
794            value /= 1000.0;
795            if (penalty_name == "stars")
796                params.penalties_star = value;
797            else if (penalty_name == "labels")
798                params.penalties_label = value;
799            else if (penalty_name == "nebulae")
800                params.penalties_nebula = value;
801            else if (penalty_name == "boundaries")
802                params.penalties_boundary = value;
803            else if (penalty_name == "boundaries_rim")
804                params.penalties_boundary_rim = value;
805            else if (penalty_name == "constellation_lines")
806                params.penalties_cline = value;
807            else if (penalty_name == "constellation_lines_rim")
808                params.penalties_cline_rim = value;
809            else if (penalty_name == "threshold")
810                params.penalties_threshold = value;
811            else if (penalty_name == "rim")
812                params.penalties_rim = value;
813            else throw string("Undefined \"penalties\" construct"
814                              " in input script: \"") + penalty_name + '"';
815        }
816
817@ @q(@> The most important filename is ``\.{output}''.  By default it's unset
818so that the output is sent to standard output.  With $$\hbox{\.{filename output
819orion.tex}}$$ the output is written to \.{orion.tex}.  Most of the other
820filenames denote the data files.  Their file format is described at the
821functions that read them.  Their names are: ``\.{stars}'', ``\.{nebulae}'',
822``\.{label\_dimensions}'', ``\.{constellation\_lines}'', ``\.{boundaries}'',
823and ``\.{milky\_way}''.
824
825``\.{latex\_preamble}'' is a file with a \LaTeX\ excerpt with a preamble
826fragment for the \LaTeX\ output.  See |@<|create_preamble()| for writing the
827\LaTeX\ preamble@>|.
828
829``\.{include}'' denotes a file that is included at the current position as an
830insert script file.  This command particularly makes sense at the very
831beginning of the input script because then you can overwrite the values
832locally.  Note that you can only include zero or one file, and included script
833files must not contain further \.{include}s.  Apart from that included scripts
834have the same structure as usual scripts.  (This is also true for a possible
835`\.{objects\_and\_labels}' part.)
836
837@q;@>
838
839The meaning of this is of course to write a master script with global settings
840(e.\,g.\ colour, line style, data file names etc.), and to overwrite them for
841certain regions of the sky, typically stellar constellations.
842
843@.output@>
844@.stars@>
845@.nebulae@>
846@.label\_dimensions@>
847@.constellation\_lines@>
848@.boundaries@>
849@.milky\_way@>
850@.latex\_preamble@>
851@.include@>
852
853@<Set filename parameters@>=
854        if (opcode == "filename") {
855            string object_name;
856            script >> object_name;
857            if (object_name == "output")
858                params.filename_output = read_string(script);
859            else if (object_name == "stars")
860                params.filename_stars = read_string(script);
861            else if (object_name == "nebulae")
862                params.filename_nebulae = read_string(script);
863            else if (object_name == "label_dimensions")
864                params.filename_dimensions = read_string(script);
865            else if (object_name == "constellation_lines")
866                params.filename_lines = read_string(script);
867            else if (object_name == "boundaries")
868                params.filename_boundaries = read_string(script);
869            else if (object_name == "milky_way")
870                params.filename_milkyway = read_string(script);
871            else if (object_name == "latex_preamble")
872                params.filename_preamble = read_string(script);
873            else if (object_name == "include") {
874                if (!params.filename_include.empty())
875                    throw string("Nesting depth of include files greater"
876                                 " than one");
877                params.filename_include = read_string(script);
878                ifstream included_script(params.filename_include.c_str());
879                if (!included_script.good())
880                    cerr << "pp3: Warning: included file "
881                         << params.filename_include
882                         << " not found; ignored" << endl;
883                else read_parameters_from_script(included_script);
884            }
885            else throw string("Undefined \"filename\" construct"
886                              " in input script: \"") + object_name + '"';
887        }
888
889
890@ Most of these values are numeric, only \.{constellation} is a string, namely
891a three-letter all-uppercase astronomic abbreviation of the constellation to be
892highlighted.  It's default is ``\.{ORI}''  but you may set it to the empty
893string with $$\hbox{\.{set constellation ""}}$$ so no constellation gets
894highlighted.  At the moment highlighting means that the boundaries have a
895brighter colour than normal.
896
897{\sloppy\raggedright
898``\.{center\_rectascension}'' and ``\.{center\_declination}'' are the celestial
899coordinates of the view frame centre.  ``\.{box\_width}'' and
900``\.{box\_height}'' are the dimensions of the view frame in centimetres.
901``\.{grad\_per\_cm}'' is the magnification (scale).
902``\.{star\_scaling}'' denotes a radial scaling of the star
903disks. ``\.{fontsize}'' is the global \LaTeX\ font size (in points).  It must
904be 10, 11, or~12.  Default is~10.
905
906Many parameters deal with the graphical representation of stars and nebulae:
907``\.{shortest\_constellation\_line}'' is the shortest length for a
908constellation line that is supposed to be drawn.  ``\.{label\_skip}'' is the
909distance between label and celestial object.  ``\.{minimal\_nebula\_radius}''
910is the radius under which a nebula is drawn as a mere circle of {\it that\/}
911radius.  ``\.{minimal\_star\_radius}'' is the radius of the smallest stellar
912dots of the graphics.  All these parameters are measured in centimetres.
913
914But there are also some magnitudes: The faintest stellar clusters that are
915drawn by default are of the ``\.{faintest\_cluster\_magnitude}'', all other
916nebulae drawn by default of the ``\.{faintest\_diffuse\_nebula\_magnitude}''.
917Stars brighter than ``\.{faintest\_star\_magnitude}'' are drawn at all, if they
918are even brighter than ``\.{faintest\_star\_with\_label\_magnitude}'' they get
919a label.  Stars brighter than ``\.{faintest\_star\_disk\_magnitude}'' are not
920just mere dots in the background, but get a radius according to their
921brightness.\par}
922
923Many of these parameters trigger the default behaviour that you can overrule by
924commands in the second part of the input script.
925
926@.set@>
927@.center\_rectascension@>
928@.center\_declination@>
929@.box\_width@>
930@.box\_height@>
931@.grad\_per\_cm@>
932@.shortest\_constellation\_line@>
933@.label\_skip@>
934@.minimal\_nebula\_radius@>
935@.faintest\_cluster\_magnitude@>
936@.faintest\_diffuse\_nebula\_magnitude@>
937@.faintest\_star\_magnitude@>
938@.minimal\_star\_radius@>
939@.faintest\_star\_disk\_magnitude@>
940@.faintest\_star\_with\_label\_magnitude@>
941@.star\_scaling@>
942@.constellation@>
943@.fontsize@>
944
945@q)'@>
946
947@<Set single value parameters@>=
948        if (opcode == "set") {
949            string param_name;
950            script >> param_name;
951            if (param_name == "center_rectascension")
952                script >> params.center_rectascension;
953            else if (param_name == "center_declination")
954                script >> params.center_declination;
955            else if (param_name == "box_width")
956                script >> params.view_frame_width;
957            else if (param_name == "box_height")
958                script >> params.view_frame_height;
959            else if (param_name == "grad_per_cm")
960                script >> params.grad_per_cm;
961            else if (param_name == "constellation")
962                params.constellation = read_string(script);
963            else if (param_name == "shortest_constellation_line")
964                script >> params.shortest_constellation_line;
965            else if (param_name == "label_skip")
966                script >> params.label_skip;
967            else if (param_name == "minimal_nebula_radius")
968                script >> params.minimal_nebula_radius;
969            else if (param_name == "faintest_cluster_magnitude")
970                script >> params.faintest_cluster_magnitude;
971            else if (param_name == "faintest_diffuse_nebula_magnitude")
972                script >> params.faintest_diffuse_nebula_magnitude;
973            else if (param_name == "faintest_star_magnitude")
974                script >> params.faintest_star_magnitude;
975            else if (param_name == "minimal_star_radius")
976                script >> params.minimal_star_radius;
977            else if (param_name == "faintest_star_disk_magnitude")
978                script >> params.faintest_star_disk_magnitude;
979            else if (param_name == "faintest_star_with_label_magnitude")
980                script >> params.faintest_star_with_label_magnitude;
981            else if (param_name == "star_scaling")
982                script >> params.star_scaling;
983            else if (param_name == "fontsize")
984                script >> params.font_size;
985            else
986                throw string("Undefined \"set\" construct in input script: \"")
987                    + param_name + '"';
988        }
989
990@* Part~II: Change printed objects and labels.  Here I read and interpret the
991second part of the input script, {\it after\/} the |"objects_and_labels"|.
992This part doesn't need to be available, and both parts may be empty.
993
994First I define a type that is often used in the following routines for a
995mapping from a catalogue number on the index in \PPTHREE/'s internal |vectors|.
996This makes access a lot faster.
997
998@<Routines for reading the input script@>=
999typedef map<int,int> index_list;
1000
1001@ Here I create the data structures that make the above mentioned mapping
1002possible.  FixMe: They should be defined globally, so that the constellation
1003lines construction can profit by it, too.  And then they needn't be given in
1004the parameter lists of the routines.
1005
1006This mapping is not vital for the program, but the alternative would be to look
1007through the whole of |nebulae| or |stars| to find a star with a certain \NGC/
1008or \HD/ number.  This is probably way to inefficient.
1009
1010@q'@>
1011
1012@<Create mapping structures for direct catalogue access@>=
1013    const int max_ngc = 7840, max_ic = 5386, max_messier = 110;
1014    index_list ngc, ic, messier;
1015    for (int i = 0; i < nebulae.size(); i++) {
1016        if (nebulae[i].ngc > 0 && nebulae[i].ngc <= max_ngc)
1017            ngc[nebulae[i].ngc] = i;
1018        if (nebulae[i].ic > 0 && nebulae[i].ic <= max_ic)
1019            ic[nebulae[i].ic] = i;
1020        if (nebulae[i].messier > 0 && nebulae[i].messier <= max_messier)
1021            messier[nebulae[i].messier] = i;
1022    }
1023    index_list henry_draper;
1024    map<string, index_list> flamsteed;
1025    for (int i = 0; i < stars.size(); i++) {
1026        if (stars[i].hd > 0) henry_draper[stars[i].hd] = i;
1027        if (stars[i].flamsteed > 0)
1028            flamsteed[stars[i].constellation][stars[i].flamsteed] = i;
1029    }
1030
1031
1032@ This is a general warning producing routine.  Such tests are vital,
1033especially if the nebulae file has been deleted in order to save disk space.  I
1034want it to be a warning because it would be very annowing to delete all such
1035opcodes from an input script only because one wants to dispense with e.\,g.\
1036nebulae.
1037
1038I need three incarnations of this function: One for nebulae, one for Flamsteed
1039number, and one for Henry Draper catalogue numbers.
1040
1041@<Routines for reading the input script@>=
1042bool assure_valid_catalogue_index(const int index, const index_list& catalogue,
1043                                  const string catalogue_name) {
1044    if (catalogue.find(index) == catalogue.end()) {
1045        cerr << "pp3: Warning: Invalid " << catalogue_name << " index: "
1046             << index << ".\n";
1047        return false;
1048    } else return true;
1049}
1050
1051bool assure_valid_catalogue_index(const int index,
1052                                  const string constellation,
1053                                  map<string,index_list>& flamsteed) {
1054    bool found = true;
1055    if (flamsteed.find(constellation) == flamsteed.end()) found = false;
1056    else if (flamsteed[constellation].find(index) ==
1057             flamsteed[constellation].end()) found = false;
1058    if (!found) cerr << "pp3: Warning: Invalid Flamsteed number: "
1059                     << index << ".\n";
1060    return found;
1061}
1062
1063bool assure_valid_catalogue_index(const int index,
1064                                  const index_list& henry_draper) {
1065    if (henry_draper.find(index) == henry_draper.end()) {
1066        cerr << "pp3: Warning: Invalid HD number: "
1067             << index << ".\n";
1068        return false;
1069    } else return true;
1070}
1071
1072@ In this routine I scan a list of stellar objects, given by a token pair of
1073catalogue name and catalogue index.  Such lists are used after some top-level
1074commands below.  A mandatory `\.{;}' ends such a list.  Five catalogues are
1075supported: \NGC/, \IC/, Messier, Henry-Draper~(\HD/), and Flamsteed numbers (in
1076the form ``{\it Constellation}\SP{\it Flamsteed number}'').  You may use the
1077program `Celestia' to get the \HD/ numbers for the stars.
1078
1079@<Routines for reading the input script@>=
1080void search_objects(istream& script,
1081                    index_list& ngc, index_list& ic,
1082                    index_list& messier, index_list& henry_draper,
1083                    map<string, index_list>& flamsteed,
1084                    vector<int>& found_stars, vector<int>& found_nebulae) {
1085    found_stars.clear();
1086    found_nebulae.clear();
1087    string catalogue_name;
1088    int catalogue_index;
1089    script >> catalogue_name;
1090    while (script && catalogue_name != ";") {
1091        script >> catalogue_index;
1092        if (catalogue_index <= 0) {
1093            stringstream error_message;
1094            error_message << "Invalid index: " << catalogue_index;
1095            throw error_message.str();
1096        }
1097        if (!script) throw string("Unexpected end of input script");
1098
1099        if (params.nebulae_visible ||
1100            (catalogue_name != "NGC" && catalogue_name != "IC" &&
1101             catalogue_name != "M")) {
1102            if (catalogue_name == "NGC") {
1103                if (assure_valid_catalogue_index(catalogue_index,ngc,"NGC"))
1104                    found_nebulae.push_back(ngc[catalogue_index]);
1105            } else if (catalogue_name == "IC") {
1106                if (assure_valid_catalogue_index(catalogue_index,ic,"IC"))
1107                    found_nebulae.push_back(ic[catalogue_index]);
1108            } else if (catalogue_name == "M") {
1109                if (assure_valid_catalogue_index(catalogue_index,messier,"M"))
1110                    found_nebulae.push_back(messier[catalogue_index]);
1111            } else if (catalogue_name == "HD") {
1112                if (assure_valid_catalogue_index(catalogue_index,henry_draper))
1113                    found_stars.push_back(henry_draper[catalogue_index]);
1114            } else {
1115                if (assure_valid_catalogue_index(catalogue_index,
1116                                                 catalogue_name,flamsteed))
1117                    found_stars.
1118                        push_back(flamsteed[catalogue_name][catalogue_index]);
1119            }
1120        }
1121        script >> catalogue_name;
1122    }
1123}
1124
1125@ This routine essentially does the same as the prevous one, however only for
1126{\it one\/} celestial object.  This is used for commands that don't take an
1127object list but only one object.
1128
1129@q'@>
1130
1131@<Routines for reading the input script@>=
1132view_data* identify_object(istream& script, index_list& ngc,
1133                           index_list& ic, index_list& messier,
1134                           index_list& henry_draper,
1135                           map<string, index_list>& flamsteed,
1136                           stars_list& stars, nebulae_list& nebulae) {
1137    string catalogue_name;
1138    int catalogue_index;
1139    script >> catalogue_name >> catalogue_index;
1140    if (!script) throw string("Unexpected end of input script");
1141    if (catalogue_name == "NGC") {
1142        if (assure_valid_catalogue_index(catalogue_index,ngc,"NGC"))
1143            return &nebulae[ngc[catalogue_index]];
1144    } else if (catalogue_name == "IC") {
1145        if (assure_valid_catalogue_index(catalogue_index,ic,"IC"))
1146            return &nebulae[ic[catalogue_index]];
1147    } else if (catalogue_name == "M") {
1148        if (assure_valid_catalogue_index(catalogue_index,messier,"M"))
1149            return &nebulae[messier[catalogue_index]];
1150    } else if (catalogue_name == "HD") {
1151        if (assure_valid_catalogue_index(catalogue_index,henry_draper))
1152            return &stars[henry_draper[catalogue_index]];
1153    } else {
1154        if (assure_valid_catalogue_index(catalogue_index,
1155                                         catalogue_name,flamsteed))
1156            return &stars[flamsteed[catalogue_name][catalogue_index]];
1157   }
1158   return 0;
1159}
1160
1161@ Here now the main routine for the second part of the input script.  The
1162top-level commands in this section are: ``\.{reposition}'',
1163``\.{delete\_labels}'', ``\.{add\_labels}'', ``\.{delete}'', ``\.{add}'',
1164``\.{set\_text\_label}'', and ``\.{text}''.
1165
1166@.reposition@>
1167@.delete\_labels@>
1168@.add\_labels@>
1169@.delete@>
1170@.add@>
1171@.text@>
1172@.set\_label\_text@>
1173
1174@<Routines for reading the input script@>=
1175void read_objects_and_labels(istream& script,
1176                             const dimensions_list& dimensions,
1177                             objects_list& objects, stars_list& stars,
1178                             nebulae_list& nebulae,
1179                             texts_list& texts,
1180                             flexes_list& flexes,
1181                             const transformation& mytransform,
1182                             bool included = false) {
1183    if (!params.filename_include.empty() && !included) {
1184        ifstream included_file(params.filename_include.c_str());
1185        @<Skip everything till |"objects_and_labels"|@>@;
1186        read_objects_and_labels(included_file, dimensions, objects, stars,
1187                                nebulae, texts, flexes, mytransform, true);
1188    }
1189    string opcode;
1190    script >> opcode;
1191    if (!script) return;
1192    @<Create mapping structures for direct catalogue access@>@;
1193    while (script) {
1194        if (opcode[0] == '#') {   // skip comment line
1195            string rest_of_line;
1196            getline(script,rest_of_line);
1197        } else
1198            @<Label repositioning@>@;
1199        else
1200            @<Change label text@>@;
1201        else
1202            @<Text labels@>@;
1203        else {  // command with objects list
1204            vector<int> found_stars, found_nebulae;
1205            @<Label deletion@>@;
1206            else
1207                @<Label activation@>@;
1208            else
1209                @<Celestial object deletion@>@;
1210            else
1211                @<Celestial object activation@>@;
1212            else throw string("Undefined opcode in input script: \"")
1213                + opcode + '"';
1214        }
1215        script >> opcode;
1216    }
1217}
1218
1219@ The following algorithm is so bad that it must be considered a bug.  It
1220simply searches for the string |"objects_and_labels"| in the |included_file|,
1221but that may occur within a string or wherever.  The file should be properly
1222scanned, but I'm too lazy for that.  A case where this bug becomes visible
1223should be very rare anyway.
1224
1225FixMe: Fix this bug, or at least for strings.  Maybe the format of input
1226scripts must be changed significantly for this.
1227
1228@q'@>
1229
1230@<Skip everything till |"objects_and_labels"|@>=
1231        string token;
1232        included_file >> token;
1233        while (included_file) {
1234            if (token[0] == '#') getline(included_file,token);
1235            else if (token == "objects_and_labels") break;
1236            included_file >> token;
1237        }
1238
1239
1240@ Sometimes labels have an unfortunate position.  But you may say e.\,g.\
1241$$\hbox{\.{reposition M 42 E ;}}$$ to position the label for the Orion Nebula
1242to the right of it.  (Abbreviations are taken from the wind rose.)  You may use
1243this command to force a certain label to be drawn although \PPTHREE/ has
1244decided that there is no space for it and didn't print it in the first place.
1245
1246If you write ``\.{E\UL}'' or ``\.{W\UL}'' the label text is not vertically
1247centred but aligned with the celestrial coordinates at the {\it baseline\/} of
1248the label text.
1249
1250At the moment, this command takes exactly one argument.  However the closing
1251semicolon is necessary.
1252
1253@q'@>
1254
1255@<Label repositioning@>=
1256        if (opcode == "reposition") {
1257            string position, semicolon;
1258            view_data* current_object =
1259                identify_object(script, ngc, ic, messier, henry_draper,
1260                                flamsteed, stars, nebulae);
1261            int angle;
1262            bool on_baseline = false;
1263            script >> position >> semicolon;
1264            if (semicolon != ";")
1265                throw string("Expected \";\" after \"reposition\" command");
1266            @<Map a wind rose |position| to an |angle|
1267              and determine |on_baseline|@>@;
1268            if (current_object) {
1269                current_object->label_angle = angle;
1270                current_object->with_label = visible;
1271                current_object->on_baseline = on_baseline;
1272                current_object->label_arranged = true;
1273            }
1274        }
1275
1276@ @<Map a wind rose |position| to an |angle| and determine |on_baseline|@>=
1277            if (position == "E" || position == "E_") angle = 0;
1278            else if (position =="NE") angle = 1;
1279            else if (position =="N") angle = 2;
1280            else if (position =="NW") angle = 3;
1281            else if (position =="W" || position == "W_") angle = 4;
1282            else if (position =="SW") angle = 5;
1283            else if (position =="S") angle = 6;
1284            else if (position =="SE") angle = 7;
1285            else throw string("Undefined position angle: ") + position;
1286            on_baseline = position[position.length()-1] == '_';
1287
1288
1289@ \PPTHREE/ reads default labels from the stars and nebulae data files.  But
1290you may say e.\,g.\ $$\hbox{\.{set\_label\_text ORI 19 Rigel}}$$ to rename
1291``$\alpha$'' (Orionis) to ``Rigel''.
1292
1293@<Change label text@>=
1294        if (opcode == "set_label_text") {
1295            view_data* current_object =
1296                identify_object(script, ngc, ic, messier, henry_draper,
1297                                flamsteed, stars, nebulae);
1298            if (current_object) current_object->label = read_string(script);
1299        }
1300
1301
1302@ With e.\,g.\ $$\hbox{\.{delete\_labels  M 35  M 42 ;}}$$ you delete the labels
1303(not the nebulae themselves!)\ of M\,35 and M\,42.
1304
1305@<Label deletion@>=
1306            if (opcode == "delete_labels") {
1307                search_objects(script, ngc, ic, messier, henry_draper,
1308                               flamsteed, found_stars, found_nebulae);
1309                for (int i = 0; i < found_stars.size(); i++) {
1310                    stars[found_stars[i]].with_label = hidden;
1311                    stars[found_stars[i]].label_arranged = true;
1312                }
1313                for (int i = 0; i < found_nebulae.size(); i++) {
1314                    nebulae[found_nebulae[i]].with_label = hidden;
1315                    nebulae[found_nebulae[i]].label_arranged = false;
1316                }
1317            }
1318
1319@ The counterpart of \.{delete\_labels}.  It makes sense first and foremost for
1320stars.  (Unfortunately this means that you have to use extensively the Henry
1321Draper Catalogue.)
1322
1323@<Label activation@>=
1324            if (opcode == "add_labels") {
1325                search_objects(script, ngc, ic, messier, henry_draper,
1326                               flamsteed, found_stars, found_nebulae);
1327                for (int i = 0; i < found_stars.size(); i++)
1328                    stars[found_stars[i]].with_label = visible;
1329                for (int i = 0; i < found_nebulae.size(); i++)
1330                    nebulae[found_nebulae[i]].with_label = visible;
1331            }
1332
1333@ This adds objects (mostly nebulae) the the field.  Notice that this object is
1334then printed even if it lies outside the view frame (it may be clipped though).
1335
1336@<Celestial object activation@>=
1337            if (opcode == "add") {
1338                search_objects(script, ngc, ic, messier, henry_draper,
1339                               flamsteed, found_stars, found_nebulae);
1340                for (int i = 0; i < found_stars.size(); i++)
1341                    stars[found_stars[i]].in_view = visible;
1342                for (int i = 0; i < found_nebulae.size(); i++)
1343                    nebulae[found_nebulae[i]].in_view = visible;
1344            }
1345
1346@ The opposite of |@<Celestial object activation@>|.
1347
1348@<Celestial object deletion@>=
1349            if (opcode == "delete") {
1350                search_objects(script, ngc, ic, messier, henry_draper,
1351                               flamsteed, found_stars, found_nebulae);
1352                for (int i = 0; i < found_stars.size(); i++)
1353                    stars[found_stars[i]].in_view = hidden;
1354                for (int i = 0; i < found_nebulae.size(); i++)
1355                    nebulae[found_nebulae[i]].in_view = hidden;
1356            }
1357
1358@ This is the only way to add a text label.  The parameters are the text
1359itself, rectascension, declination, \RGB/ colour, and the relative position
1360(uppercase wind rose), followed by a semicolon.  For example,
1361$$\hbox{\.{text Leo at 11 10 color 1 0 0 towards S ;}}$$ puts a red ``Leo''
1362centered below the point $(11\,\hbox{h}, +10^\circ)$ in the Lion.  You may
1363leave out the ``\.{color}'' and/or the ``\.{towards}'' option.  The default
1364colour is the last given colour in a previous \.{text} command, or if this
1365doesn't exist the current text label colour.  The default value of
1366``\.{towards}'' is~|"NE"|.
1367
1368The contents of a text label is eventually in an \.{\BS hbox}, so you can use
1369that fact.  You can also use all \PS/Tricks commands.  For example, with
1370\medskip
1371
1372{\parindent2em\tentt
1373text "\BS\BS psdots[dotstyle=+,dotangle=45](0,0)\BS\BS scriptsize\BS\BS{} N
1374           pole of ecliptic"\par
1375\ \ \ \ \ at 18 66.56 towards E\UL{} ;
1376}
1377
1378\medskip\noindent the $\times$ marks the northern ecliptical pole, together
1379with a small label text.  If such things occur frequently, define it as a
1380\LaTeX\ macro.
1381
1382If you write ``\.{E\UL}'' or ``\.{W\UL}'' the label text is not vertically
1383centred but aligned with the celestrial coordinates at the {\it baseline\/} of
1384the label text.
1385
1386\medskip A ``declination flex'' is a special text label that stands on a circle
1387of equal declination which can look very nice.  You get it with the option
1388``\.{along declination}'' in the parameter list of the text label.
1389
1390\medskip If you use the \.{tics} option the label is printed along a
1391rectascension or declination circle multiple times, making it possible to print
1392tic marks.  For example, \medskip
1393
1394{\parindent2em\tentt
1395text "\$\#3\$" at 0 20 along declination tics rectascension 1 towards N ;\par
1396text "\$\#5\$" at 11 0 along declination tics declination 10 towards S ;
1397}
1398
1399\medskip\noindent creates automatically generated labels for the $20^\circ$
1400declination circle (every whole hour), and for the $11^{\sevenrm h}$
1401rectascension line (every 10 declination degrees).  See the explanation for
1402|@<Generate |contents| from current coordinates@>| for further details.
1403
1404@.text@>
1405@.along@>
1406@.declination@>
1407@.tics@>
1408
1409@<Text labels@>=
1410if (opcode == "text") {
1411    string token, contents, position("NE");
1412    double rectascension, declination;
1413    contents = read_string(script);
1414    script >> token;
1415    if (token != "at") throw string("\"at\" in \"text\" command expected");
1416    script >> rectascension >> declination;
1417    script >> token;
1418    int angle = 1;  // |"NE"|
1419    bool on_baseline = false;
1420    enum { kind_text_label, kind_flex_declination} label_kind
1421        = kind_text_label;
1422    double step_rectascension = -1.0; // $-1$ means that no maks are produced.
1423    double step_declination = -1.0;
1424    while (script && token != ";") {
1425        if (token == "color") script >> params.textlabelcolor;
1426        else if (token == "towards") {
1427            script >> position;
1428            @<Map a wind rose |position| to an |angle|
1429              and determine |on_baseline|@>@;
1430        }
1431        else if (token == "along") {
1432            script >> token;
1433            if (token == "declination") {
1434                label_kind = kind_flex_declination;
1435            } else throw string("Invalid \"along\" option");
1436        }
1437        else if (token == "tics") {
1438            script >> token;
1439            if (token == "rectascension") {
1440                script >> step_rectascension;
1441                if (step_rectascension <= 0.0)
1442                    throw string("Invalid \"tics\" interval");
1443                step_declination = -1.0;
1444            } else if (token == "declination") {
1445                script >> step_declination;
1446                if (step_declination <= 0.0)
1447                    throw string("Invalid \"tics\" interval");
1448                step_rectascension = -1.0;
1449            } else throw string("Invalid \"tics\" option");
1450        } else throw string("Invalid \"text\" option");
1451        script >> token;
1452    }
1453    if (!script)
1454        throw string("Unexpected end of script while scanning \"text\"");
1455    if (step_rectascension <= 0.0 && step_declination <= 0.0) {
1456        @<Insert text label into label container structure@>@;
1457    } if (step_rectascension > 0.0) {
1458        string user_pattern(contents);
1459        double start = rectascension - floor(rectascension/step_rectascension)
1460                                         * step_rectascension;
1461        for (rectascension = start; rectascension < 24.0;
1462             rectascension += step_rectascension) { cerr << rectascension << ' ';
1463            @<Generate |contents| from current coordinates@>@;
1464            @<Insert text label into label container structure@>@;
1465        }
1466    } else if (step_declination > 0.0) {
1467        string user_pattern(contents);
1468        double start = declination - floor((declination+90.0) / step_declination)
1469                                         * step_declination;
1470        for (declination = start; declination <= 90.0;
1471             declination += step_declination) { cerr << declination << ' ';
1472            @<Generate |contents| from current coordinates@>@;
1473            @<Insert text label into label container structure@>@;
1474        }
1475    }
1476}
1477
1478@ FixMe: Eventually this section must be a clean function call.  I insert a
1479text label or a flex into the correct data structure.
1480
1481@<Insert text label into label container structure@>=
1482    switch (label_kind) {
1483    case kind_text_label:
1484        texts.push_back(text(contents, rectascension, declination,
1485                             params.textlabelcolor, angle, on_baseline));
1486        break;
1487    case kind_flex_declination:
1488        flexes.push_back(new declination_flex(contents, rectascension,
1489                                              declination,
1490                                              params.textlabelcolor, angle,
1491                                              on_baseline));
1492        break;
1493    }
1494
1495@ FixMe: Eventually this section must be a clean function call.  Here I create
1496the \LaTeX\ macro for the tics marks.  It is called \.{\BS coordinates} and has
1497nine parameters, however not all are used so far:\medskip
1498
1499\item{\tt\#1} Rectascension in hours.
1500\item{\tt\#2} Declination in degrees.
1501\item{\tt\#3} Rectascension in integer hours (truncated, not rounded).
1502\item{\tt\#4} Rectascension fraction of hour in minutes (truncated, not
1503              rounded).
1504\item{\tt\#5} Declination in rounded integer degrees.
1505
1506\medskip All decimal points are replaced by ``\.{\{\\DP\}}'' commands.
1507
1508@<Generate |contents| from current coordinates@>=
1509        {
1510            stringstream coordinates;
1511            coordinates.setf(ios::fixed);
1512            coordinates << "\\def\\coordinates#1#2#3#4#5#6#7#8#9{\\TicMark{"
1513                        << user_pattern << "}}";
1514            coordinates << "\\coordinates{" << rectascension << "}{"
1515                        << declination << "}{";
1516            coordinates << int(floor(rectascension)) << "}{"
1517                        << int(floor((rectascension - floor(rectascension))
1518                                     * 60.0))
1519                        << "}{";
1520            if (int(declination) > 0.0) coordinates << '+';
1521            coordinates << int(declination) << "}{}{}{}{}";
1522            contents = coordinates.str();
1523            while (contents.find(".") != string::npos)
1524                contents.replace(contents.find("."),1,"{\\DP}");
1525        }
1526
1527@** Data structures and file formats.  First I describe the data structures that
1528directly contain celestial objects such as stars and nebulae.  This is a little
1529bit unfortunate for the program itself, because actually other things should be
1530defined first (e.\,g.\ |dimension|).  However I think that the program can be
1531digested more easily this way.
1532
1533All data structures are |struct|s and no |class|es.  The reason is that they
1534are all very simple and all object oriented security measures are only
1535hindering.
1536
1537For each data type called {\bf classname} I define a container called {\bf
1538classnames\_list} that is a |vector|.  For almost each data type I define a
1539routine that can read it from a file in the vector.  There I also decribe the
1540respective file format.
1541
1542@* View data: Positioning and labels.  This first |struct| can be used as an
1543ancestor in the various celestial objects data structurs (e.\,g.\ stars) for
1544containing all view frame dependent information.  Most importantly it contains
1545the label of a celestial object, its position relatively to the object, and its
1546size.
1547
1548|in_view| is |visible| if the object is actually displayed on screen.  |x| and
1549 |y| contain the screen coordinates in centimetres.  |radius| is the radial
1550 size of the object in centimetres.  |skip| is given in centimetres, too.  It
1551 denotes the space between the outer boundary of the object (enclosed by
1552 |radius|) and the label.
1553
1554|with_label| is |visible| if the object has a label, with |label_width|,
1555 |label_height|, |label_depth| (estimated in centimetres) and |label_angle|.
1556 {\it Please note the all heights in \PPTHREE/ are {\rm total} heights, thus
1557 the same as \TeX's height${}+{}$depth!}
1558
1559|on_baseline| is true, if the {\it baseline\/} of the label text is supposed to
1560 be vertically aligned with the celestial position.  (This works not for all
1561 |label_angle|s.)
1562
1563|label_arranged| is |true| if the best place for the label has been found
1564 already.  Only then the label should be really printed, but the real use of
1565 |label_arranged| is that it avoids the label to be arranged
1566 twice. |lable_angle| can only have eight possible values: 0:~$0^\circ\!$,
1567 1:~$45^\circ\!$, 2:~$90^\circ\!$, 3:~$135^\circ\!$, 4:~$180^\circ\!$,
1568 5:~$225^\circ\!$, 6:~$270^\circ\!$, and 7:~$315^\circ\!$.
1569
1570|scope()| returns the maximal scope of the object.  It is used in |@<Find
1571objects in vicinity of |objects[i]|@>| to find all objects in the vicinity of a
1572given on-object.
1573
1574@q;@>
1575
1576@c
1577typedef enum { @!hidden, @!visible, @!undecided } visibility;
1578
1579struct view_data {
1580    visibility in_view;
1581    double x,y;
1582    double radius, skip;
1583    visibility with_label;
1584    bool label_arranged;
1585    string label;
1586    double label_width, label_height, label_depth;
1587    bool on_baseline;
1588    color label_color;
1589    int label_angle;
1590    view_data() : in_view(undecided), x(DBL_MAX), y(DBL_MAX), radius(0.0),
1591                  skip(params.label_skip),
1592                  with_label(undecided), label_arranged(false), label(),
1593                  label_width(0.0), label_height(0.0),
1594                  label_depth(0.0), on_baseline(false),
1595                  label_color(params.labelcolor), label_angle(0) { }
1596    void get_label_boundaries(double& left,double& right,double& top,
1597                              double& bottom) const;
1598    double scope() const { return radius + skip +
1599                               my_fmax(label_width,label_height) + 2.0*skip; }
1600    bool has_valid_coordinates() const { return x != DBL_MAX && y != DBL_MAX; }
1601    virtual double penalties_with(const double left, const double right,
1602                                  const double top, const double bottom,
1603                                  bool core = true) const;
1604};
1605
1606@ This is the only structure that is not put into a container directly, but via
1607references.  The reason is the virtual routine |penalties_with()|; I want to
1608use polymorphy.
1609
1610@c
1611typedef vector<view_data*> objects_list;
1612
1613@ Each label is stored in |view_data| by its dimensions, its |skip|, the
1614|radius| of the object itself, and the |label_angle|.  While these quantities
1615are very convenient to use for the positioning of the label, they are pretty
1616unfortunate if you want to know which coordinates are occupied by the label in
1617order to find out possible overlaps.
1618
1619Therefore I calculate here the rectangular boundaries of a label.  They are
1620stored in |left|, |right|, |top|, and |bottom| in screen centimetres.
1621
1622@c
1623void view_data::get_label_boundaries(double& left,double& right,double& top,
1624                                     double& bottom) const {
1625    const double origin_x =
1626        x + (radius + skip) * cos(M_PI_4 * double(label_angle));
1627    const double origin_y =
1628        y + (radius + skip) * sin(M_PI_4 * double(label_angle));
1629    switch (label_angle) {
1630        case 0: case 1: case 7: left = origin_x; break;
1631        case 2: case 6: left = origin_x - label_width / 2.0; break;
1632        case 3: case 4: case 5: left = origin_x - label_width; break;
1633    }
1634    right = left + label_width;
1635    switch (label_angle) {
1636    case 0: case 4: bottom = origin_y -
1637                             (on_baseline? 0.0 : label_height / 2.0); break;
1638    case 1: case 2: case 3: bottom = origin_y; break;
1639    case 5: case 6: case 7: bottom = origin_y - label_height; break;
1640    }
1641    if (on_baseline) bottom -= label_depth;
1642    top = bottom + label_height;
1643}
1644
1645@ Every object of type |view_data| (or a descendant of it) must be able to
1646calculate the overlap of itself with a rectangle given by |left|, |right|,
1647|top|, and |bottom|.  It must then create a penalty value out of it.  Normally
1648this is just the overlap itself in square centimetres, like here.
1649
1650@c
1651double view_data::penalties_with(const double left, const double right,
1652                                 const double top, const double bottom,
1653                                 bool core) const {
1654    if (with_label == visible && label_arranged) {
1655        double left2, right2, top2, bottom2;
1656        get_label_boundaries(left2,right2,top2,bottom2);
1657        const double overlap_left = my_fmax(left, left2);
1658        const double overlap_right = my_fmin(right, right2);
1659        const double overlap_top = my_fmin(top, top2);
1660        const double overlap_bottom = my_fmax(bottom, bottom2);
1661        const double overlap_x = my_fdim(overlap_right, overlap_left);
1662        const double overlap_y = my_fdim(overlap_top, overlap_bottom);
1663        return overlap_x * overlap_y * params.penalties_label;
1664    } else return 0.0;
1665}
1666
1667@ The book claims that the following routines (well, without the |my_|) are
1668part of the {\mc GNU} \CEE/~Library version~2.2 beta.  However, I didn't find
1669them.
1670
1671@q'@>
1672
1673@<Missing math routines@>=
1674inline double my_fmin(const double& x, const double& y) {
1675    return x < y ? x : y;
1676}
1677
1678inline double my_fmax(const double& x, const double& y) {
1679    return x > y ? x : y;
1680}
1681
1682inline double my_fdim(const double& x, const double& y) {
1683    return x > y ? x - y : 0.0;
1684}
1685
1686
1687@* Stars.  The actual star data is -- like all other user defined data
1688structure in this program -- organised as a |struct| because it's too simple
1689for a |class|.  |hd| is the Henry Draper Catalog number, |bs| is the Bright
1690Star Catalog number.  |rectascension| is given in hours, |declination| in
1691degrees.  |b_v| is the B$-$V brightness in magnitudes (equals
1692$m_{\hbox{\sevenrm phot}}-m_{\hbox{\sevenrm vis}}$) and thus contains colour.
1693|name| either contains the Bayer name (only the Greek letter and maybe an
1694exponent number), or, if this doesn't exist, the Flamsteed number as a string.
1695|spectral_class| is the complete spectral class, starting with |"F6"| or
1696whatever.
1697
1698@s star int
1699@s stars_list int
1700
1701@q;@>
1702
1703@c
1704struct star : public view_data {
1705    int hd, bs;
1706    double rectascension, declination;
1707    double magnitude;
1708    double b_v;
1709    int flamsteed;
1710    string name;
1711    string constellation;
1712    string spectral_class;
1713    star() : hd(0), bs(0), rectascension(0.0), declination(0.0),
1714             magnitude(0.0), b_v(0.0), flamsteed(0), name(""),
1715             spectral_class(""), view_data() { }
1716    virtual double penalties_with(const double left, const double right,
1717                                  const double top, const double bottom,
1718                                  bool core = true) const;
1719};
1720
1721@ In order to find the penalties with a (labelled) star, I first calculate them
1722for the label itself, which may be |0.0|, in particular if |label_arranged| is
1723still |false|.  Then I determine the rectangular overlap, just like in
1724|view_data::penalties_with()|.  This is unfortunate, because stars are circles
1725and not rectangles.  FixMe: This should be done justice to.
1726
1727@c
1728double star::penalties_with(const double left, const double right,
1729                            const double top, const double bottom,
1730                            bool core) const {
1731    double penalties = view_data::penalties_with(left, right, top, bottom,
1732                                                 core);
1733    const double left2 = x - radius, right2 = x + radius, top2 = y + radius,
1734        bottom2 = y - radius;
1735    const double overlap_left = my_fmax(left, left2);
1736    const double overlap_right = my_fmin(right, right2);
1737    const double overlap_top = my_fmin(top, top2);
1738    const double overlap_bottom = my_fmax(bottom, bottom2);
1739    const double overlap_x = my_fdim(overlap_right, overlap_left);
1740    const double overlap_y = my_fdim(overlap_top, overlap_bottom);
1741    penalties += overlap_x * overlap_y * params.penalties_star;
1742    return penalties;
1743}
1744
1745
1746typedef vector<star> stars_list;
1747
1748@ I want to be able to read the star data records from a text format file.  The
1749format of the input is a text stream with the following format.  Each star
1750entry consists of four lines: \medskip
1751
1752\item{1.} A row with seven fields separated by whitespace:
1753\itemitem{--} Henry Draper Catalogue number (|int|, `|0|' if unknown),
1754\itemitem{--} \BSC/ number (|int|, `|0|' if unknown),
1755\itemitem{--} rectascension in hours (|double|),
1756\itemitem{--} declination in degrees (|double|),
1757\itemitem{--} visual brightness in magnitudes (|double|),
1758\itemitem{--} B$-$V brightness in magnitudes (|double|, `|99.0|' if unknown),
1759\itemitem{--} Flamsteed number (|int|, `|0|' if unknown).
1760\item{2.} The label (astronomical name) for the star, as a \LaTeX-ready string,
1761e.\,g.\ ``\.{\$\\alpha\$}'', ``\.{\$\\phi\^\{2\}\$}'', or simply
1762``\.{\$23\$}''.  May be the empty string.
1763\item{3.} The astronomical abbreviation of the constellation.  It must be all
1764uppercase.
1765\item{4.} The spectral class.  It must start with the spectral class letter,
1766followed by the fraction digit, followed by the luminosity class as a Roman
1767number, e.\,g.~``\.{F5III}''.  Anything may follow as in ``\.{K2-IIICa-1}'',
1768however the mandatory parts must not contain any whitespace.
1769
1770@c
1771istream& operator>>(istream& in, star& s) {
1772    in >> s.hd >> s.bs >> s.rectascension
1773       >> s.declination >> s.magnitude >> s.b_v
1774       >> s.flamsteed;
1775    char ch;
1776    do in.get(ch); while (in && ch != '\n');
1777    getline(in,s.name);
1778    getline(in,s.constellation);
1779    getline(in,s.spectral_class);
1780    return in;
1781}
1782
1783@ Here I read a set of stars from a file with the name |filename|.  The file
1784|stars_file| is a text file that consists solely of a list of |star|s.
1785
1786@c
1787void read_stars(stars_list& stars) {
1788    ifstream stars_file(params.filename_stars.c_str());
1789    if (!stars_file) throw string("No stars file found: "
1790                                  + params.filename_stars);
1791    star current_star;
1792    stars_file >> current_star;
1793    while (stars_file) {
1794        current_star.label = string("\\Starname{") + current_star.name + '}';
1795        stars.push_back(current_star);
1796        stars_file >> current_star;
1797    }
1798}
1799
1800
1801@* Nebulae.  This is also used for stellar clusters of course.  In the whole
1802program ``nebula'' denotes nebulae, galaxies, clusters, and other non-stellar
1803objects.
1804
1805@c
1806typedef enum { @!unknown, @!galaxy, @!emission, @!reflection, @!open_cluster,
1807               @!globular_cluster } nebula_kind;
1808
1809@ Since nebulae appear on the screen next to stars, and because they can have
1810labels, they are descendants of |view_data|, too.
1811
1812|ngc|, |ic|, and |messier| are of course the respective catalogue number.
1813Since it's silly that |ngc| and |ic| are defined, a value of `|0|' means that
1814the nebula is not part of the respective catalogue.  |constellation| is a
1815three-character string with the name of the respective constellation in all
1816uppercase.  |rectascension| is given in hours, |declination| in degrees.
1817|diameter_x| and |diameter_y| are the extent of the nebula in the horizonal and
1818the vertical direction and are given in degrees, |horizontal_angle| (in
1819degrees) is the angle between the horizontal axis of |diameter_x| and the
1820intersecting celestial rectascension circle.  |magnitude| (in magnitudes) is
1821the {\it total\/} brightness (not the brightness density).
1822
1823@c
1824struct nebula : public view_data {
1825    int ngc, ic, messier;  // |0| if not defined.
1826    string constellation;
1827    double rectascension, declination;
1828    double magnitude;
1829    double diameter_x, diameter_y;
1830    double horizontal_angle;
1831    nebula_kind kind;
1832    nebula() : ngc(0), ic(0), messier(0), constellation(), rectascension(0.0),
1833               declination(0.0), magnitude(0.0), diameter_x(0.0),
1834               diameter_y(0.0), horizontal_angle(0.0), kind(unknown) { }
1835    virtual double penalties_with(const double left,const double right,
1836                                  const double top, const double bottom,
1837                                  bool core = true) const;
1838};
1839
1840typedef vector<nebula> nebulae_list;
1841
1842@ In order to find the penalties with a (labelled) nebula, I first calculate
1843them for the label itself, which may be |0.0|, in particular if
1844|label_arranged| is still |false|.  Then I determine the rectangular overlap,
1845just like in |view_data::penalties_with()|.  This is unfortunate, because nebulae
1846are circles and not rectangles.  FixMe: This should be done justice to.
1847
1848@c
1849double nebula::penalties_with(const double left, const double right,
1850                              const double top, const double bottom,
1851                              bool core) const {
1852    double penalties = view_data::penalties_with(left, right, top, bottom, core);
1853    const double left2 = x - radius, right2 = x + radius, top2 = y + radius,
1854        bottom2 = y - radius;
1855    const double overlap_left = my_fmax(left, left2);
1856    const double overlap_right = my_fmin(right, right2);
1857    const double overlap_top = my_fmin(top, top2);
1858    const double overlap_bottom = my_fmax(bottom, bottom2);
1859    const double overlap_x = my_fdim(overlap_right, overlap_left);
1860    const double overlap_y = my_fdim(overlap_top, overlap_bottom);
1861    penalties += overlap_x * overlap_y * params.penalties_nebula;
1862    return penalties;
1863}
1864
1865@ As you can see, the file format for a nebulae file is very simple, because
1866there are no string fields with possible whitespace within them.  It's just a
1867stream of fields separated by whitespace.  |kind| is an |int|, and it's the
1868canonical translation of the |nebula_kind| to |int|.
1869
1870If the |horizontal_angle| is unknown, is must be~|720.0|.  |diameter_y| must
1871always have a valid value, even if it's actually unknown; in this case it must
1872be equal to |diameter_y|.
1873
1874@q'@>
1875
1876@c
1877istream& operator>>(istream& in, nebula& n) {
1878    int kind;
1879    in >> n.ngc >> n.ic >> n.messier >> n.constellation >> n.rectascension
1880       >> n.declination >> n.magnitude >> n.diameter_x >> n.diameter_y
1881       >> n.horizontal_angle >> kind;
1882    n.kind = nebula_kind(kind);
1883    return in;
1884}
1885
1886@ Here I read a set of nebulae from a file with the name |filename|.  The
1887format is just a stream of |nebula|e.
1888
1889@c
1890void read_nebulae(nebulae_list& nebulae) {
1891    ifstream nebulae_file(params.filename_nebulae.c_str());
1892    nebula current_nebula;
1893    nebulae_file >> current_nebula;
1894    while (nebulae_file) {
1895        @<Create nebula label@>@;
1896        nebulae.push_back(current_nebula);
1897        nebulae_file >> current_nebula;
1898    }
1899}
1900
1901@ In order to create a \LaTeX-ready label, I first choose which catalog to use.
1902It is from the Messier, the \NGC/, and the \IC/ catalogue the first in which
1903the nebula appears, i.\,e.\ the first non-zero catalogue number.  The catalogue
1904abbreviation itself is stored in |catalog|, whereas the |stringstream| |number|
1905contains the number within that catalogue.  I just concatenate both to the
1906|label|.
1907
1908@<Create nebula label@>=
1909        string catalogue;
1910        stringstream number;
1911        if (current_nebula.messier > 0) {
1912            catalogue = "\\Messier{";
1913            number << current_nebula.messier;
1914        } else if (current_nebula.ngc > 0) {
1915            catalogue = "\\NGC{";
1916            number << current_nebula.ngc;
1917        } else if (current_nebula.ic > 0) {
1918            catalogue = "\\IC{";
1919            number << current_nebula.ic;
1920        } else throw string("Invalid catalogue: \"") + catalogue + '"';
1921        current_nebula.label = catalogue + number.str() + '}';
1922
1923
1924@* Constellation boundaries.  They are stored in an external file called
1925\.{constborders.dat}.
1926
1927{\sloppy Is is very convenient to have a special data type for a point in the
1928program that has created \.{constborders.dat}.  In this program the advantage
1929is not so big but it's sensible to use the same data structures here.\par}
1930
1931@q'@>
1932
1933@c
1934struct point {
1935    double x,y;
1936    point(const double x, const double y) : x(x), y(y) { }
1937    point() : x(0.0), y(0.0) { }
1938};
1939
1940@ An object of type |boundary| contains one stright line of a constellation
1941boundary.  This consists of the two endpoints which come from the original
1942boundary catalog by Delporte of 1930, and zero or more interpolated points
1943calculated by Davenhall (1990).  The interpolated points help to draw a curved
1944line where this is necessary.
1945
1946{\sloppy Every line usually belongs to exactly two constellations (of course).
1947They are stored in the field |constellations|.  FixMe: There are boundaries
1948with only one owner.  I don't know how this can happen.\par}
1949
1950@s boundary int
1951@s boundaries_list int
1952
1953@q'@>
1954
1955@c
1956struct boundary {
1957    vector<point> points;
1958    vector<string> constellations;
1959    bool belongs_to_constellation(const string constellation) const;
1960};
1961
1962typedef vector<boundary> boundaries_list;
1963
1964@ In this routine I use only the first three letters of the constellation
1965abbreviation.  The reason is that the original catalog uses |"SER1"| and
1966|"SER2"| for ``Serpent Caput'' and ``Serpent Cauda''.  However, I see them as
1967one constellation.  Be aware that this routine expects the constellation
1968abbreviation in uppercase.
1969
1970@c
1971bool boundary::belongs_to_constellation(const string constellation) const {
1972    for (int i = 0; i < constellations.size(); i++)
1973        if (constellations[i].substr(0,3) == constellation.substr(0,3))
1974            return true;
1975    return false;
1976}
1977
1978@ Of course I need to be able to read the boundaries from
1979\.{constborders.dat}.  The input stream is a text stream of the following
1980format.  The set of celestial boundaries is stored as a set of elementary
1981lines without kinks. \medskip
1982
1983\item{1.} Number of points (|size|$_1$) in the line (|int|).
1984\item{2.} Repeated |size|$_1$ times:
1985\itemitem{--} rectascension of point in hours (|double|),
1986\itemitem{--} declination of point in degrees (|double|).
1987\item{3.} Number of constellations (|size|$_2$) touching this border line
1988    (|int|, should be be always |2|, however it isn't with current data).
1989\item{4.} Repeated |size|$_2$ times:
1990\itemitem{--} All uppercase astronomical abbreviation of the constellation.  It
1991may distinguish between ``\.{SER1}'' and ``\.{SER2}'' for Serpens Caput and
1992Serpens Cauda.
1993
1994\medskip All fields are separated by whitespace.
1995
1996@q')@>
1997
1998@c
1999istream& operator>>(istream& in, boundary& p) {
2000    int size;
2001    in >> size;
2002    if (!in.good()) return in;
2003    p.points.resize(size);
2004    for (int i=0; i<size; i++)
2005        in >> p.points[i].x >> p.points[i].y;
2006    in >> size;
2007    p.constellations.resize(size);
2008    for (int i=0; i<size; i++)
2009        in >> p.constellations[i];
2010    return in;
2011}
2012
2013@ Here I read a set of boundaries from a file.  It's simply a list of
2014|boundary|'s.
2015
2016@c
2017void read_constellation_boundaries(boundaries_list& boundaries) {
2018    ifstream boundaryfile(params.filename_boundaries.c_str());
2019    boundary current_boundary;
2020    boundaryfile >> current_boundary;
2021    while (boundaryfile) {
2022        boundaries.push_back(current_boundary);
2023        boundaryfile >> current_boundary;
2024    }
2025}
2026
2027@ As a big exception to the other classes, I don't derive |boundary| itself
2028from |view_data|, but its smaller brother, |boundary_atom|.  In contrast to
2029|boundary|, |boundary_atom| only contains two points of the view frame, between
2030which a boundary line will be drawn.  This is not totally accurate since
2031boundary lines are not totally straight which may cause problems at the poles.
2032However, it should be good enough.
2033
2034@s boundary_atom int
2035
2036@q'@>
2037
2038@c
2039struct boundary_atom : public view_data {
2040    point start, end;
2041    boundary_atom(point start, point end);
2042    virtual double penalties_with(const double left,const double right,
2043                                  const double top, const double bottom,
2044                                  bool core = true) const;
2045};
2046
2047@ The nice thing about |boundary_atom| is that after it has been constructed,
2048it's finished.  Nothing has to be changed any more because all is known in the
2049moment of construction.
2050
2051Since boundary atoms are only created when they are visible, I can set |in_view
2052= visible| etc. @q'@>
2053
2054@c
2055boundary_atom::boundary_atom(point start, point end) : start(start), end(end) {
2056    x = (start.x + end.x) / 2.0;
2057    y = (start.y + end.y) / 2.0;
2058    radius = hypot(end.x - start.x, end.y - start.y) / 2.0;
2059    radius *= M_PI_2;
2060    in_view = visible;
2061    with_label = hidden;
2062    skip = 0.0;
2063}
2064
2065@ The full algorithm that is used here is described in the |@<Definition of
2066|line_intersection()| for intersection of two lines@>|.  Except for peculiar
2067cases there should be exact two intersections altogether, which means that it's
2068the same as with constellation lines.
2069
2070Objects of this type are created in |@<Create a |boundary_atom| for the
2071|objects|@>|.
2072
2073@q'@>
2074
2075@c
2076@<Definition of |line_intersection()| for intersection of two lines@>@;@#
2077
2078double boundary_atom::penalties_with(const double left,const double right,
2079                                     const double top, const double bottom,
2080                                     bool core)
2081    const {
2082    double intersection;
2083    point r(end.x - start.x, end.y - start.y);
2084    vector<point> intersection_points;
2085    if (line_intersection(left - start.x, r.x, start.y, r.y,
2086                          bottom, top, intersection))
2087        intersection_points.push_back(point(left, intersection));
2088    if (line_intersection(right - start.x, r.x, start.y, r.y,
2089                          bottom, top, intersection))
2090        intersection_points.push_back(point(right, intersection));
2091    if (line_intersection(top - start.y, r.y, start.x, r.x,
2092                          left, right, intersection))
2093        intersection_points.push_back(point(intersection, top));
2094    if (line_intersection(bottom - start.y, r.y, start.x, r.x,
2095                          left, right, intersection))
2096        intersection_points.push_back(point(intersection, bottom));
2097    if (start.x > left && start.x < right && start.y > bottom && start.y < top)
2098        intersection_points.push_back(start);
2099    if (end.x > left && end.x < right && end.y > bottom && end.y < top)
2100        intersection_points.push_back(end);
2101    if (intersection_points.empty()) return 0.0;
2102    if (intersection_points.size() != 2) {
2103        cerr << "pp3: Funny " << intersection_points.size()
2104             << "-fold constellation boundary intersecrtion." << endl;
2105        return 0.0;
2106    }
2107    const double length = hypot(intersection_points[1].x -
2108                                intersection_points[0].x,
2109                                intersection_points[1].y -
2110                                intersection_points[0].y);
2111    return (core? 8.0 * params.penalties_boundary :
2112            0.5 * params.penalties_boundary_rim) / 72.27*2.54 * length;
2113}
2114
2115@* Constellation lines.  This is not about the {\it boundaries}, but about the
2116connection lines between the main stars of a given constellation.  They are
2117mere eye candy.  I call their |struct| type ``connections'' to keep the name
2118unique and concise.
2119
2120A |connection| consists of |lines|.  The point coordinates are screen
2121coordinates in centimetres.  |from| and |to| are the star star and the end
2122star, given by their index in |stars|.
2123
2124Notice that |start| and |end| aren't defined before
2125|draw_constellation_lines()| has been called, {\it and\/} the constellation
2126line is actually visible.  Then they contain the screen coordinates of the
2127start and the end point of the line.
2128
2129@s connection int
2130
2131@q'@>
2132
2133@c
2134struct connection : public view_data {
2135    point start, end;
2136    int from, to;
2137    connection(const int from, const int to)
2138        : from(from), to(to), start(), end() { in_view = visible;
2139        with_label = hidden; skip = 0.0; }
2140    virtual double penalties_with(const double left,const double right,
2141                                  const double top, const double bottom,
2142                                  bool core = true) const;
2143};
2144
2145typedef vector<connection> connections_list;
2146
2147@ In order to calculate penalties, we need some sort of overlap.  Since there
2148is no area overlap between a line and a rectangle, I need the overlapping line
2149length.
2150
2151This is the first part of the algorithm.  It is actually pretty simple, however
2152hard to explain.  We have two lines to intersect: One edge of the label
2153rectangle and the constellation line.  The rectangle is given by |left|,
2154|right|, |top|, and |bottom| -- as usual.  The constellation line is given by
2155their start point |start| and end point |end|.  Or, in parameterised form:
2156%
2157$$\vec x = {\hbox{|start.x|} \choose \hbox{|start.y|}} + \lambda
2158{\hbox{|end.x|} - \hbox{|start.x|} \choose \hbox{|end.y|} - \hbox{|start.y|}},
2159\qquad \lambda\in[0;1].$$
2160%
2161The edge of the rectangle is given by (left edge as
2162example)
2163%
2164$$\eqalign{\left(\vec x - {\hbox{|left|} \choose 0}\right) \cdot
2165{1\choose0} &= 0 \cr \noalign{\vskip0.5ex} \Rightarrow\quad \hbox{|start.x|} +
2166\lambda (\hbox{|end.x|} - \hbox{|start.x|}) &= \hbox{|left|}\cr
2167\mathrel{\mathop\Leftrightarrow^{end.x - start.x \ne 0}\lambda = {\hbox{|left|}
2168- \hbox{|start.x|} \over \hbox{|end.x|} - \hbox{|start.x|}}}}$$
2169%
2170with the
2171boundary condition $\hbox{|bottom|} < \hbox{|start.y|} + \lambda
2172(\hbox{|end.y|} - \hbox{|start.y|}) =: \hbox{|intersection|} < \hbox{|top|}$.
2173With the abbreviations/\hskip0ptassignments (also exemplarily for the `left'
2174case)
2175%
2176$$\eqalign{\hbox{|numerator|} &= \hbox{|left|} - \hbox{|start.x|},\cr
2177\hbox{|denominator|} &= \hbox{|end.x|} - \hbox{|start.x|},\cr
2178\hbox{|zero_point|} &= \hbox{|start_y|},\cr \hbox{|slope|} &= \hbox{|end.y|} -
2179\hbox{|start.y|},\cr \hbox{|min|} &= \hbox{|bottom|},\quad \hbox{|max|} =
2180\hbox{|top|}}$$
2181%
2182we get the following routine for finding out whether a certain
2183label rectangle edge is intersected by the constellation line or not:
2184
2185@q')@>
2186
2187@<Definition of |line_intersection()| for intersection of two lines@>=
2188bool line_intersection(double numerator, double denominator,
2189                       double zero_point, double slope,
2190                       double min, double max,
2191                       double& intersection) {
2192    if (denominator == 0) return false;
2193    const double lambda = numerator / denominator;
2194    intersection = zero_point + lambda * slope;
2195    return lambda > 0.0 && lambda < 1.0 &&
2196        intersection > min && intersection < max;
2197}
2198
2199@ Now for the second part of the intersection algorithm.  Here I apply the
2200preceding routing on all four label edges.  I return an overlap as the penalty
2201value, however I pretend that the line has a width of 8\,pt.  For an overlap
2202with the rim, it's only 0.5\,pt.
2203
2204@q'@>
2205
2206@c
2207double connection::penalties_with(const double left, const double right,
2208                                  const double top, const double bottom,
2209                                  bool core) const
2210{
2211    double intersection;
2212    point r(end.x - start.x, end.y - start.y);
2213    vector<point> intersection_points;
2214    if (line_intersection(left - start.x, r.x, start.y, r.y,
2215                          bottom, top, intersection))
2216        intersection_points.push_back(point(left, intersection));
2217    if (line_intersection(right - start.x, r.x, start.y, r.y,
2218                          bottom, top, intersection))
2219        intersection_points.push_back(point(right, intersection));
2220    if (line_intersection(top - start.y, r.y, start.x, r.x,
2221                          left, right, intersection))
2222        intersection_points.push_back(point(intersection, top));
2223    if (line_intersection(bottom - start.y, r.y, start.x, r.x,
2224                          left, right, intersection))
2225        intersection_points.push_back(point(intersection, bottom));
2226    if (start.x > left && start.x < right && start.y > bottom && start.y < top)
2227        intersection_points.push_back(start);
2228    if (end.x > left && end.x < right && end.y > bottom && end.y < top)
2229        intersection_points.push_back(end);
2230    if (intersection_points.empty()) return 0.0;
2231    if (intersection_points.size() != 2) {
2232        cerr << "pp3: Funny " << intersection_points.size()
2233             << "-fold constellation line intersecrtion." << endl;
2234        return 0.0;
2235    }
2236    const double length = hypot(intersection_points[1].x -
2237                                intersection_points[0].x,
2238                                intersection_points[1].y -
2239                                intersection_points[0].y);
2240    return (core? 8.0 * params.penalties_cline :
2241            0.5 * params.penalties_cline_rim) / 72.27*2.54 * length;
2242}
2243
2244@ I must be able to read a file which contains such data.  Here, too, the text
2245file format is very simple: It's a list of constellation line paths separated
2246by `\.{;}'.  A star name must be of the form $$\eqalign{\noalign{\hbox{{\it
2247Constellation}\SP{\it Flamsteed number}}} \noalign{\hbox{or}}
2248\noalign{\hbox{\.{HD}\SP{\it Henry-Draper Catalogue number}}}}$$ where {\it
2249Constellation\/} must be given as an all uppercase three letter abbreviation.
2250For example, \.{ORI\SP19} is $\alpha$~Ori (Rigel).
2251
2252The hash sign `\.{\#}' introduces comments that are ignored till end of line,
2253however they mustn't occur within a star.
2254
2255@c
2256void read_constellation_lines(connections_list& connections,
2257                              const stars_list& stars) {
2258    ifstream file(params.filename_lines.c_str());
2259    string from_catalogue_name, to_catalogue_name;
2260    int from_catalogue_index = 0, to_catalogue_index = 0;
2261    file >> to_catalogue_name;
2262    while (file) {
2263        if (to_catalogue_name[0] == '#') { // skip comment
2264            string dummy;
2265            getline(file,dummy);
2266        } else if (to_catalogue_name == ";")  // start a new path
2267            from_catalogue_index = 0;
2268        else {
2269            file >> to_catalogue_index;
2270            if (from_catalogue_index > 0 && to_catalogue_index > 0) {
2271                @<Create one connection@>@;
2272            }
2273            from_catalogue_name = to_catalogue_name;
2274            from_catalogue_index = to_catalogue_index;
2275        }
2276        file >> to_catalogue_name;
2277    }
2278}
2279
2280@ In the loop I try to find the index within |stars| of the `from' star and the
2281`to'~star.
2282
2283@<Create one connection@>=
2284            int from_index = -1, to_index = -1;
2285            for (int i = 0; i < stars.size(); i++) {
2286                @<Test whether |stars[i]| is the `from' star@>@;
2287                @<Test whether |stars[i]| is the `to' star@>@;
2288            }
2289            if (from_index == -1 || to_index == -1) {
2290                stringstream error_message;
2291                error_message << "Unrecognised star in constellation lines: ";
2292                if (from_index == 0)
2293                    error_message << from_catalogue_name << ' '
2294                                  << from_catalogue_index;
2295                else
2296                    error_message << to_catalogue_name << ' '
2297                                  << to_catalogue_index;
2298                throw error_message.str();
2299            }
2300            connections.push_back(connection(from_index, to_index));
2301
2302@ Here I test whether the current star in the loop is the `from' star.  Of
2303course only if I haven't found it already (|from_index == 0|).  If apparently
2304both stars have been found already, I leave the loop immediately.
2305
2306@q;@>
2307
2308@<Test whether |stars[i]| is the `from' star@>=
2309@q'@>
2310                if (from_index == -1)
2311                    if (from_catalogue_name == "HD") {
2312                        if (stars[i].hd == from_catalogue_index) from_index = i;
2313                    } else {
2314                        if (from_catalogue_name == stars[i].constellation)
2315                            if (stars[i].flamsteed == from_catalogue_index)
2316                                from_index = i;
2317                    } else if (to_index != -1) break;
2318
2319@ Perfectly analogous to |@<Test whether |stars[i]| is the `from' star@>|.
2320
2321@q'@>
2322
2323@<Test whether |stars[i]| is the `to' star@>=
2324@q'@>
2325                if (to_index == -1)
2326                    if (to_catalogue_name == "HD") {
2327                        if (stars[i].hd == to_catalogue_index) to_index = i;
2328                    } else {
2329                        if (to_catalogue_name == stars[i].constellation)
2330                            if (stars[i].flamsteed == to_catalogue_index)
2331                                to_index = i;
2332                    } else if (from_index != -1) break;
2333
2334
2335@* The Milky Way.  The file is a text file as usual with the following
2336structure, everything separated by whitespace: \medskip
2337
2338\item{1.} The maximal ($={}$equatorial) diagonal half distance of two pixels in
2339degrees (|double|).  This value is used as the |radius| for the milky way
2340`pixels'.  Of course it must be the minimal radius for which there are no gaps
2341between the pixels.
2342
2343\item{2.} The pixels themselves with two |double|s and one |int| each:
2344\itemitem{--} The rectascension in hours.
2345\itemitem{--} The declination in degrees.
2346\itemitem{--} The grey value of the pixel from $1$ to~$255$.  Zero is not used
2347because zero-value pixels are not included into the data file anyway.
2348
2349In \PPTHREE/'s standard distribution, this file was produced with the helper
2350program \.{milkydigest.cc}, and the original Milky Way bitmap was photographed
2351and compiled by \pdfURL{Axel
2352Mellinger}{http://home.arcor-online.de/axel.mellinger/}.
2353
2354|pixels| is a |vector<vector<point> >|.  The outer (first) index is the grey
2355 value, and for every grey value there is an inner vector (second index) with a
2356 list of all points (rectascension, declination) that have this value.  This
2357 construction makes the drawing in Ghostview looking nice, but more importantly
2358 it reduces the number of colour change commands in the \LaTeX\ file.
2359 Unfortunately this doesn't reduce pool size usage.
2360
2361@<Reading the milkyway into |pixels|@>=
2362    ifstream file(params.filename_milkyway.c_str());
2363    double radius;
2364    file >> radius;
2365    double rectascension, declination, x, y;
2366    int pixel;
2367    const double cm_per_grad = 1.0 /
2368        (mytransform.get_rad_per_cm() * 180.0 / M_PI);
2369    radius *= cm_per_grad / 2.54 * 72.27;
2370    file >> rectascension >> declination >> pixel;
2371    while (file) {
2372        if (mytransform.polar_projection(rectascension, declination, x,y))
2373            pixels[pixel].push_back(point(x,y));
2374        file >> rectascension >> declination >> pixel;
2375    }
2376
2377
2378@* Colour.  It's very convenient to have a unified data structure for all
2379colours that appear in this program.  Its internal structure is trivial, and I
2380only support the \RGB/ colour model.  The only complicated thing is |name|
2381here.  I need it because of \PS/Tricks' way to activate colours: They must get
2382names first.  I could get rid of it if I called all colours e.\,g.\
2383``\.{tempcolor}'' or ``\.{dummycolor}'' and activated them at once.  But this
2384is not necessary.
2385
2386@s color int
2387
2388@<Define |color| data structure@>=
2389struct color {
2390    double red, green, blue;
2391    string name;
2392    color(string name, double red, double green, double blue)
2393        : red(red), green(green), blue(blue), name(name) { }
2394    color(double red, double green, double blue)
2395        : red(red), green(green), blue(blue), name() { }
2396    void set(ostream& out) const;
2397};
2398
2399@ This routine is used when the name of the colour is not necessary, because
2400it's only needed locally.  This is the case for text labels and the Milky Way
2401dots.
2402
2403@q'@>
2404
2405@c
2406void color::set(ostream& out) const {
2407    out << "\\newrgbcolor{dummycolor}{" << red << ' ' << green << ' ' << blue
2408        << "}\\dummycolor\n  \\psset{linecolor=dummycolor}%\n";
2409}
2410
2411@ Both output and input of |color|s is asymmetric: When I {\it read\/} them I
2412assume that I do it from an input script.  Then it's a mere sequence of the
2413three colour values.
2414
2415@q'@>
2416
2417@c
2418istream& operator>>(istream& in, color& c) {
2419    in >> c.red >> c.green >> c.blue;
2420    if (!in @/
2421        || c.red < 0.0 || c.red > 1.0 @/
2422        || c.green < 0.0 || c.green > 1.0 @/
2423        || c.blue < 0.0 || c.blue > 1.0)
2424        throw string("Invalid RGB values in input script");
2425    return in;
2426}
2427
2428@ But when I {\it write\/} them, I assume that I do it into a \LaTeX\ file with
2429\PS/Tricks package activated.  Then I deploy a complete \.{\\newrgbcolor}
2430command.
2431
2432@c
2433ostream& operator<<(ostream& out, const color& c) {
2434    if (c.name.empty()) throw string("Cannot write unnamed color to stream");
2435    out << "\\newrgbcolor{" << c.name << "}{" << c.red << ' ' << c.green
2436        << ' ' << c.blue << "}%\n";
2437    return out;
2438}
2439
2440@ This routine is hitherto only used when drawing the milky way.  It helps to
2441find a colour between the two extremes |c1| and~|c2|.  The value of |x| is
2442always between $0$ and~$1$ and denotes the point on the way between |c1| and
2443|c1| in the \RGB/ colour space where the new colour should be.  I interpolate
2444linearly.  In order to create a new colour object, I need a |new_name| for it,
2445too.
2446
2447@c
2448color interpolate_colors(const double x, const color c1, const color c2,
2449                         const string new_name = "") {
2450    if (x < 0.0 || x > 1.0) throw string("Invalid x for color interpolation");
2451    const double y = 1.0 - x;
2452    return color(new_name,
2453                 y * c1.red + x * c2.red,
2454                 y * c1.green + x * c2.green,
2455                 y * c1.blue + x * c2.blue);
2456}
2457
2458
2459@q@@** Ephemerides.@>
2460
2461
2462@** Drawing the chart.  This is done in two steps.  First the lines and the
2463celestial objects are printed.  During this phase a lot of labels may
2464accumulate.  They cannot be drawn at that phase, because the arrangement with
2465minimal overlaps can only be calculated when all are known.
2466
2467Therefore I have to fill a container called |objects| which contains elements
2468of type |view_data|.  The part of each |star| or |nebula| or whatever that is
2469|view_data| is appended to |objects|, if and only if the object is visible in
2470the view frame.  The typical command for that is
2471$$\hbox{|objects.push_back(&stars[i]);|}$$ (here for stars).
2472Please notice that it is {\it not\/} important whether or not the respective
2473object bears a label.  Its data is needed in any case.
2474
2475In the second phase I arrange and print the labels.
2476
2477@* Coordinate transformations.  They are done by the class |transformation|.
2478|width| and |height| contain the view frame dimensions in centimetres.
2479|rad_per_cm| is the resolution.
2480
2481The $3\times3$ matrices |a| and |a_unscaled| are rotation matrices for the
2482transformation in cartesian space from the equatorial system into the azimuthal
2483system, where the center of the view frame is the $z$ axis.  While |a_unscaled|
2484refers to a celestial sphere with radius~$1$, |a| contains the additional
2485scaling for the actual centimetres on the paper.
2486
2487@s transformation int
2488
2489@c
2490class transformation {
2491    double width, height, rad_per_cm;
2492    double a[3][3], a_unscaled[3][3];
2493    inline double stretch_factor(double z) const;
2494public:
2495    transformation(const double rectascension,
2496                   const double declination,
2497                   const double width, const double height,
2498                   const double grad_per_cm);
2499    bool polar_projection(const double rectascension, const double declination,
2500                          double& x, double& y) const;
2501    double get_rad_per_cm() const @+ { @+ return rad_per_cm; @+ }
2502};
2503
2504@ Starting point is the parallel projection of a celestial unit sphere on the
2505$x$-$y$ plane.  This projection looks like a sky globus seen from far away with
2506a strong zoom objective.
2507
2508It is the starting point because it simply is a necessary intermediate step:
2509All celestial positions given in polar coordinates ({\it rectascension}, {\it
2510declination\/}) must be transformed to cartesian coordinates in order to apply
2511a rotation on them for having the center of the view frame in the center of the
2512coordinate system.  From there it's a trivial step to the parallel projection.
2513
2514But it squeezes the rim areas too much, which I don't like.
2515
2516By |stretch| I mean the radial stretch factor that should relieve this
2517distortion.  If |stretch| was |1.0|, we would get the mentioned parallel
2518projection.
2519
2520\def\frac#1#2{{#1\over#2}}
2521
2522Therefore I stretch the plot here so that this effect is minimised.  The result
2523is the equidistant azimuthal projection that can be described best if you
2524imagine a plot with its center exactly on the north pole: All circles of equal
2525declination have then the same distance from each other.  So the plot keeps its
2526circular form.  The {\it radial\/} scale is constant everywhere and equal to
2527|grad_per_cm|.  Perpendicular to that, the scale is decreasing from
2528|grad_per_cm| (center) to $2/\pi\cdot{}$|grad_per_cm| (border).  The exact
2529relation is $$\eqalignno{{\it scale}_\parallel &= \hbox{|grad_per_cm|},\cr {\it
2530scale}_\perp &= \hbox{|grad_per_cm|} \cdot {r \over
2531\arccos\sqrt{1-r^2}},\quad\hbox{and thus}\cr \noalign{\vskip0.5ex} {\it
2532stretch} &= {\arccos\sqrt{1-r^2} \over r}. &(1)}$$ $r$ is simply the distance
2533from the center of the plot and it is $1$ on the border.  But I don't have $r$,
2534I have $z$.  I could to the substitution $r = \sqrt{1-z^2}$ leading to a very
2535badly converging Taylor series, but much better is $\tilde z = 1 - z$ and to
2536use a Taylor series of $${\it stretch} = {\arccos (1-\tilde z) \over \sqrt{1-
2537(1-\tilde z)^2}}.$$ Maple\,V says $${\it stretch} = 1+1/3\,\tilde
2538z+2/15\,{\tilde z}^{2}+{\frac {2}{35}}\,{\tilde z}^{3} + {\frac
2539{8}{315}}\,{\tilde z}^ {4} + {\frac{8}{693}}\,{\tilde z}^{5} + {\cal O}({\tilde
2540z}^{11/2}).$$ This is good enough (error less than 1\,\%).  There are two
2541alternatives:
2542
2543\medskip \item{1.} Use the Tayor expansion of~(1) directly, because this
2544expansion converges very quickly, especially for the center-near regions.  This
2545would mean to calculate $r(z)$ for every point, but this shouldn't be too
2546costly.  \item{2.} Transform back in spherical coordinates, re-interpret them
2547as planar polar coordinates and transform them to planar cartesian.  Probably
2548too difficult.  \medskip
2549
2550Why not calculating~(1) (maybe with a subsitution) directly without series
2551expansion?  First, it may be too costly.  But secondly, I don't like that in
2552the particularly interesting region close to the center of the view frame both
2553numerator and denominator get close to $0$, and eventually they do reach it.
2554Maybe I'm paranoid, but I don't like that.
2555
2556\def\zt{\tilde z}
2557
2558@s zt TeX
2559
2560@q'@>
2561
2562@c
2563inline double transformation::stretch_factor(const double z) const {
2564    const double zt = 1.0 - z;
2565    return 1.0 + zt *
2566        ( 1.0/3.0 + zt *
2567          ( 2.0/15.0 + zt *
2568            ( 2.0/35.0 + zt *
2569              ( 8.0/315.0 + zt *
2570                ( 8.0/693.0 )))));
2571}
2572
2573@ This is not only the constructor for |transformation|, it is also the
2574initialiser for the whole transformation.  As much work as possible is this
2575routine, in order to keep calculations easy in the function
2576|polar_projection()| which will be called hundreds if not thousands of times.
2577
2578|rectascension| is given in hours, together with |declination| it's the center
2579of the desired view frame.  |width| and |height| give its dimensions in
2580centimetres, |grad_per_cm| is the resulting resolution in the center.
2581$$\hbox{|a_unscaled|} = \pmatrix{\sin\varphi & \cos\varphi & 0\cr
2582\cos\varphi\cos\alpha & -\sin\varphi\cos\alpha & \sin \alpha\cr
2583-\cos\varphi\sin\alpha & \sin\varphi\sin\alpha & \cos\alpha}.$$
2584
2585@q'@>
2586
2587@c
2588transformation::transformation(const double rectascension,
2589                               const double declination,
2590                               const double width, const double height,
2591                               const double grad_per_cm) {
2592    const double phi = - (rectascension + 12) * 15.0 * M_PI / 180.0;
2593    const double delta = declination * M_PI / 180.0;
2594    const double alpha = - delta + M_PI_2;
2595    rad_per_cm = grad_per_cm * M_PI / 180.0;
2596    a_unscaled[0][0] = sin(phi);
2597    a_unscaled[0][1] = cos(phi);
2598    a_unscaled[0][2] = 0.0;
2599    a_unscaled[1][0] = cos(phi) * cos(alpha);
2600    a_unscaled[1][1] = -sin(phi) * cos(alpha);
2601    a_unscaled[1][2] = sin(alpha);
2602    a_unscaled[2][0] = -cos(phi) * sin(alpha);
2603    a_unscaled[2][1] = sin(phi) * sin(alpha);
2604    a_unscaled[2][2] = cos(alpha);
2605    for (int i = 0; i < 3; i++)
2606        for (int j = 0; j < 3; j++)
2607            a[i][j] = a_unscaled[i][j] / rad_per_cm;
2608    transformation::width = width;
2609    transformation::height = height;
2610}
2611
2612@ Here I transform the equatorial position $(\hbox{|rectascension|},
2613\hbox{|declination|})$ to the cartesian position $(x,y)$.  The resulting
2614cartesian positions represents an azimuthal equidistant projection with the
2615center of the view frame (see |rectascension| and |declination| in the
2616constructor of |transformation|).
2617
2618It returns |true| if the resulting point is within the view frame, otherwise
2619|false|.
2620
2621@c
2622bool transformation::polar_projection(const double rectascension,
2623                                      const double declination,
2624                                      double& x, double& y) const {
2625    const double phi = rectascension * 15.0 * M_PI / 180.0;
2626    const double delta = declination * M_PI / 180.0;
2627    const double cos_delta = cos(delta);
2628
2629    const double x0 = cos_delta * cos(phi);
2630    const double y0 = cos_delta * sin(phi);
2631    const double z0 = sin(delta);
2632
2633    const double z1 =
2634        a_unscaled[2][0] * x0 + a_unscaled[2][1] * y0 + a_unscaled[2][2] * z0;
2635    if (z1 < -DBL_EPSILON) return false;
2636    const double stretch = stretch_factor(z1);
2637    const double x1 = a[0][0] * x0 + a[0][1] * y0;
2638    const double y1 = a[1][0] * x0 + a[1][1] * y0 + a[1][2] * z0;
2639    x = x1 * stretch + width / 2.0;
2640    y = y1 * stretch + height / 2.0;
2641    if (x < 0.0 || x > width || y < 0.0 || y > height) return false;
2642    return true;
2643}
2644
2645
2646@* Label organisation.  Without labels, star charts are not very useful.  But
2647labels mustn't overlap, and they should not overlap with other chart elements
2648such as star circles or nebula circles.  Here I try to develop a simple
2649algorithm that is able to avoid most of these problems.  There are two main
2650routines here: |arrange_labels()| and |print_labels()|.
2651
2652|arrange_labels()| modifies the |label_angle| field in each celestial object
2653in order to avoid any overlap with other objects, namely other labels, stars,
2654or nebulae.  It does so by testing all eight values for |label_angle| and
2655calculating a |penalty| value for each of them.  This |penalty| is
2656$$\hbox{|penalty|} = \hbox{\it overlap}_{\hbox{\sevenrm labels}} + \hbox{\it
2657overlap}_{\hbox{\sevenrm objects}} + \hbox{\it penalty}_{\hbox{\sevenrm
2658lines}}.$$
2659
2660|print_labels()| actually generates the \LaTeX\ code for printing them.
2661
2662@q'@>
2663
2664@ First the routine that actually calculates the overlap.  It simply finds the
2665common rectangular area in squared centimetres.  Both rectangles are given by
2666their boundaries, |left1|, |right1|, |top1|, |bottom1| enclose the first
2667rectangle, |left2|, |right2|, |top2|, |bottom2| the second.
2668
2669@c
2670double calculate_overlap(double left1, double right1, double top1,
2671                         double bottom1, double left2, double right2,
2672                         double top2, double bottom2) {
2673    const double overlap_left = my_fmax(left1, left2);
2674    const double overlap_right = my_fmin(right1, right2);
2675    const double overlap_top = my_fmin(top1, top2);
2676    const double overlap_bottom = my_fmax(bottom1, bottom2);
2677    const double overlap_x = my_fdim(overlap_right, overlap_left);
2678    const double overlap_y = my_fdim(overlap_top, overlap_bottom);
2679    return overlap_x * overlap_y;
2680}
2681
2682@ Nor for a structure with the real dimensions in centimetres of all possible
2683labels.  They are read from the file \.{labeldims.dat} and stored in a
2684|dimensions_list|.
2685
2686@s dimensions_list int
2687@s dimension int
2688@q'@>
2689
2690@c
2691struct dimension {
2692    double width, height, depth;
2693    dimension() :  width(0.0), height(0.0), depth(0.0) { }
2694    dimension(const dimension& d) : width(d.width), height(d.height),
2695                                    depth(d.depth) { }
2696};
2697
2698typedef map<string,dimension> dimensions_list;
2699
2700@ Now we re-arrange the labels, namely |objects[i].with_label| for all~|i|.
2701For efficiency, I first find all neighbours of the on-object and do all the
2702following work only with them.  In the inner |k|-loop I test all possible
2703|label_angle|s and calculate their |penalty|.
2704
2705If a label leaps out of the view frame, this adds to |penalty| the gigantic
2706value of |10000.0|.
2707
2708@c
2709@<|create_preamble()| for writing the \LaTeX\ preamble@>@#@;
2710@<Helping routines for nebulae labels@>@#@;
2711
2712void arrange_labels(objects_list& objects, dimensions_list& dimensions) {
2713    objects_list vicinity;
2714    @<Set label dimensions@>@;
2715    for (int i = 0; i < objects.size(); i++) {
2716        vicinity.clear();
2717        if (objects[i]->in_view == visible && objects[i]->with_label == visible
2718            && !objects[i]->label_arranged) {
2719            @<Find objects in vicinity of |objects[i]|@>@;
2720            double best_penalty = DBL_MAX;
2721            int best_angle = 0;
2722            for (int k = 0; k < 8; k ++) {
2723                objects[i]->label_angle = k;
2724                double on_object_left, on_object_right, on_object_top,
2725                    on_object_bottom;
2726                objects[i]->
2727                    get_label_boundaries(on_object_left, on_object_right,
2728                                         on_object_top, on_object_bottom);
2729                double penalty = 0.0;
2730                double rim_width = 2.0 * objects[i]->skip;
2731                for (int j = 0; j < vicinity.size(); j++) {
2732                    penalty += vicinity[j]->
2733                        penalties_with(on_object_left, on_object_right,
2734                                       on_object_top, on_object_bottom) + @|
2735                        vicinity[j]->
2736                        penalties_with(on_object_left - rim_width,
2737                                       on_object_right + rim_width,
2738                                       on_object_top + rim_width,
2739                                       on_object_bottom - rim_width,
2740                                       false) * params.penalties_rim;
2741                }
2742                if (on_object_left < 0.0 || on_object_bottom < 0.0 ||
2743                    on_object_right > params.view_frame_width ||
2744                    on_object_top > params.view_frame_height)
2745                    penalty += 10000.0;
2746                if (penalty < best_penalty) {
2747                    best_penalty = penalty;
2748                    best_angle = k;
2749                }
2750            }
2751            if (!objects[i]->label.empty())
2752                if (best_penalty < 0.4 * params.penalties_threshold *
2753                    objects[i]->label_height * objects[i]->label_width) {
2754                    objects[i]->label_angle = best_angle;
2755                    objects[i]->label_arranged = true;
2756#ifdef DEBUG
2757                    stringstream penalty;
2758                    penalty.precision(2);
2759                    penalty << " " << best_penalty << " ("
2760                            <<  best_penalty /
2761                        (objects[i]->label_height * objects[i]->label_width)
2762                        * 100 << "%)";
2763                //  |objects[i]->label += penalty.str();|
2764                    cerr << "pp3DEBUG: Object " << objects[i]->label << ' ';
2765                    const star* s = dynamic_cast<star*>(objects[i]);
2766                    if (s) cerr << s->constellation;
2767                    cerr << " has penalty of" << penalty.str() << endl;
2768#endif
2769                } else {
2770                    objects[i]->with_label = hidden;
2771                    objects[i]->label_arranged = true;
2772                }
2773        }
2774    }
2775}
2776
2777@ All objects in the vicinity of |objects[i]| eventually end up in the |vector|
2778|vicinity|.  Here I fill this structure.  I use a very rough guess for finding
2779the neighbours, so there will probably be too many of them, but it makes
2780calculation much easier.
2781
2782The practical thing is that neighbouring objects are ordered in increasing
2783brightness in star data file, which means that lables of bright stars are
2784arranged first, and labels of fainter stars must cope with these positions.
2785
2786Of course, it's guaranteed that |objects[i]| is not part of its vicinity.
2787
2788@q'@>
2789
2790@<Find objects in vicinity of |objects[i]|@>=
2791            const double on_object_scope = objects[i]->scope();
2792            for (int j = 0; j < objects.size(); j++) {
2793                if (i != j && objects[j]->in_view == visible) {
2794                    const double distance =
2795                        hypot(objects[i]->x - objects[j]->x,
2796                              objects[i]->y - objects[j]->y);
2797                    if (distance < on_object_scope + objects[j]->scope() &&
2798                        @<Condition to exclude double stars and such@>)
2799                        vicinity.push_back(objects[j]);
2800                }
2801            }
2802
2803@ @<Condition to exclude double stars and such@>=
2804(distance > objects[j]->skip ||
2805 (objects[j]->with_label == visible && !objects[j]->label.empty()))
2806
2807@ Finally I print out all labels by generation \LaTeX\ code from any of them.
2808I do that by calculating the coordinates in centimetres of the {\it bottom
2809left\/} corner of the label box, and placing the \TeX\ box there.  This \TeX\
2810box lies within another one with zero dimensions in order to keep the point of
2811origin (bottom left of the view frame) intact.
2812
2813@c
2814void print_labels(const objects_list& objects) {
2815    for (int i = 0; i < objects.size(); i++)
2816        if (objects[i]->in_view == visible && objects[i]->with_label == visible
2817            && objects[i]->label_arranged) {
2818            double left, right, top, bottom;
2819            objects[i]->get_label_boundaries(left,right,top,bottom);
2820            if (left < 0.0 || bottom < 0.0 || right > params.view_frame_width
2821                || top > params.view_frame_height) continue;
2822
2823            objects[i]->label_color.set(OUT);
2824            OUT << "\\hbox to 0pt{";
2825            OUT << "\\hskip" << left << "cm";
2826            OUT << "\\vbox to 0pt{\\vss\n  \\hbox{\\Label{";
2827            OUT << objects[i]->label;
2828            OUT << "}}\\vskip" << bottom << "cm";
2829            OUT << "\\hrule height 0pt}\\hss}%\n";
2830#ifdef DEBUG
2831            OUT << "\\psframe[linewidth=0.1pt](" << left << ',' << bottom
2832                << ")(" << right << ',' << bottom+objects[i]->label_depth << ")%\n";
2833            OUT << "\\psframe[linewidth=0.3pt](" << left << ',' << bottom
2834                << ")(" << right << ',' << top << ")%\n";
2835#endif
2836        }
2837}
2838
2839@ Here I read label dimensions from a text file.  The format is very simple:
2840\medskip
2841
2842\item{1.} The \LaTeX\ representation of the label on a line of its own.
2843\item{2.} In the following line, width, height, and depth of the label in
2844centimetres (both |double|), separated by whitespace.
2845
2846\medskip This is repeated for every data record.
2847
2848@c
2849void read_label_dimensions(dimensions_list& dimensions) {
2850    ifstream file(params.filename_dimensions.c_str());
2851    string name,dummy;
2852    getline(file,name);
2853    while (file) {
2854        file >> dimensions[name].width >> dimensions[name].height
2855             >> dimensions[name].depth;
2856        getline(file,dummy);  // Read the |'\n'|
2857        getline(file,name);
2858    }
2859}
2860
2861@*1 Determining label dimensions.  {\sloppy Here I go through all |objects| and
2862set the |label_width|, |label_height|, and |label_depth| which have been zero
2863so far.  It may happen that a label is not found (possibly because |dimensions|
2864is totally empty because no label dimensions file could be found).  In this
2865case I call |recalculate_dimensions()| to get all labels recalculated via an
2866extra \LaTeX\ run.\par}
2867
2868|dimensions_recalculated| is |true| if |recalculate_dimensions()| has been
2869 called and thus one can assume that all needed labels are now available.  It
2870 is merely to remove unnecessary tests and make the procedure faster.
2871
2872The |throw| command should never happen.  It means an internal error.
2873
2874@<Set label dimensions@>=
2875bool dimensions_recalculated = false;
2876for (int i = 0; i < objects.size(); i++) {
2877    view_data* current_object = objects[i];
2878    if (current_object->with_label == visible) {
2879        if (!dimensions_recalculated &&
2880            dimensions.find(current_object->label) == dimensions.end()) {
2881            recalculate_dimensions(dimensions,objects);
2882            dimensions_recalculated = true;
2883            if (dimensions.find(current_object->label) == dimensions.end())
2884                throw string("Update of label dimensions file failed: \"") +
2885                    current_object->label + "\" not found";
2886        }
2887        current_object->label_width = dimensions[current_object->label].width;
2888        current_object->label_height =
2889            dimensions[current_object->label].height;
2890        current_object->label_depth = dimensions[current_object->label].depth;
2891    }
2892}
2893
2894
2895@ When \PPTHREE/ is started it reads a file usually called \.{labeldimens.dat}
2896in order to know width, height, and depth (in centimetres) of all labels.  This
2897is vital for the penalty (i.\,e.\ overlap) calculations.  But it may be that a
2898label that you want to include can't be found in this file, or you have deleted
2899it because you've changed the \LaTeX\ preamble of the output (i.\,e.\ the
2900fonts).  In these cases \PPTHREE/ automatically creates a new one.  It is then
2901used in the following runs to save time.
2902
2903Here I do this.  First I store all label names (not only the missing!)\ in
2904|required_names|.  I assure that every label occurs only once.
2905
2906Then I create a |temp_file| which is a \LaTeX\ file that -- if sent through
2907\LaTeX\ -- is able to create another temporary file called |raw_labels_file|.
2908This is read and stored directly in |dimensions| (where it belongs to
2909naturally).
2910
2911Finally, a new updated |cooked_labels_file| (aka \.{labeldimens.dat}) is
2912created.
2913
2914\medskip
2915By the way, I am forced to use {\it two\/} temporary files.  It is impossible
2916to let the \LaTeX\ file create directly the file \.{labeldimens.dat}.  The
2917reason are the label string:  They may contain characters that have a special
2918meaning in \LaTeX, and I'm unable to avoid any tampering.
2919
2920@q'@>
2921
2922@<Helping routines for nebulae labels@>=
2923void recalculate_dimensions(dimensions_list& dimensions,
2924                            const objects_list& objects)
2925{
2926    list<string> required_names;
2927    for (int i = 0; i < objects.size(); i++) {
2928        const string current_name = objects[i]->label;
2929        if (!current_name.empty()) required_names.push_back(current_name);
2930    }
2931    required_names.unique();
2932
2933    ofstream temp_file("pp3temp.tex");
2934    create_preamble(temp_file);
2935    temp_file << "\n\\begin{document}\n"
2936              << "\\newwrite\\labelsfile\n"
2937              << "\\catcode`\\_=11  % for underscores in the filename\n"
2938              << "\\immediate\\openout\\labelsfile=pp3temp.dat\n"
2939              << "\\catcode`\\_=8\n";
2940    list<string>::const_iterator p = required_names.begin();
2941    while (p != required_names.end())
2942        temp_file << "\\setbox0 = \\hbox{\\Label{" << *(p++)
2943                  << "}}\n  \\immediate\\write\\labelsfile{"
2944                     "\\the\\wd0s \\the\\ht0s \\the\\dp0s}\n";
2945    temp_file << "\\immediate\\closeout\\labelsfile\n\\end{document}\n";
2946    temp_file.close();
2947    string commandline("latex pp3temp");
2948    if (params.filename_output.empty())
2949        commandline += " > pp3dump.log";
2950    if (system(commandline.c_str()) != 0)
2951        throw string("Label dimensions calculations: LaTeX call failed: ")
2952            + commandline;
2953
2954    ifstream raw_labels_file("pp3temp.dat");
2955    p = required_names.begin();
2956    while (p != required_names.end()) {
2957        string current_width, current_height, current_depth;
2958        string current_name;
2959        current_name = *(p++);
2960        raw_labels_file >> current_width >> current_height >> current_depth;
2961        current_width.substr(0,current_width.length() - 3);
2962        current_height.substr(0,current_height.length() - 3);
2963        current_depth.substr(0,current_depth.length() - 3);
2964        dimensions[current_name].width = strtod(current_width.c_str(), 0)
2965            / 72.27 * 2.54;
2966        dimensions[current_name].height = strtod(current_height.c_str(), 0)
2967            / 72.27 * 2.54;
2968        dimensions[current_name].depth = strtod(current_depth.c_str(), 0)
2969            / 72.27 * 2.54;
2970        dimensions[current_name].height += dimensions[current_name].depth;
2971    }
2972
2973    @<Write updated \.{labeldimens.dat} file@>@;
2974}
2975
2976@ Here I create the new, updated |cooked_labels_file| (aka
2977\.{labeldimens.dat}).  So all already calculated dimensions can be used for the
2978following runs.
2979
2980@<Write updated \.{labeldimens.dat} file@>=
2981    ofstream cooked_labels_file("labeldimens.dat");
2982    cooked_labels_file.setf(ios::fixed);
2983    cooked_labels_file.precision(5);
2984    dimensions_list::const_iterator q = dimensions.begin();
2985    while (q != dimensions.end()) {
2986        string current_name;
2987        dimension current_dimension;
2988        current_name = q -> first;
2989        current_dimension = (q++) -> second;
2990        cooked_labels_file << current_name << '\n'
2991                           << current_dimension.width << ' '
2992                           << current_dimension.height << ' '
2993                           << current_dimension.depth
2994                           << '\n';
2995    }
2996
2997
2998@*1 User labels.  The user should be able to insert arbitaray text into the
2999chart.  The code for this is provided here.  The data type of them, |text|, is
3000of course a classical derivative of |view_data|.  It only holds the celestial
3001coordinates and the text (\LaTeX) string of the label.
3002
3003@s text int
3004
3005@c
3006struct text : public view_data {
3007    string contents;
3008    double rectascension, declination;
3009    text(string t, double r, double d, color c, int angle, bool on_baseline);
3010};
3011
3012typedef list<text> texts_list;
3013
3014@ In the constructor I modify the values of some |view_data| elements, because
3015a |text| is a special label.  For example it mustn't be arranged, because it is
3016already.  And it gets another colour (if wanted).
3017
3018@q'@>
3019
3020@c
3021text::text(string t, double r, double d, color c, int angle, bool on_baseline)
3022    : contents(t), rectascension(r), declination(d) {
3023    label = string("\\TextLabel{") + t + '}';
3024    with_label = visible;
3025    label_angle = angle;
3026    view_data::on_baseline = on_baseline;
3027    label_arranged = true;
3028    label_color = c;
3029    radius = skip = 0.0;
3030}
3031
3032@ This routine is called |draw_|\dots\ due to its analogy to the other drawing
3033function.  But curiously enough, I don't draw anything here, because labels are
3034drawn in |print_labels()| and |text|s consists only of labels.  I only have to
3035assure that I don't include invisible labels.
3036
3037@c
3038void draw_text_labels(transformation& mytransform, texts_list& texts,
3039                      objects_list& objects) {
3040    texts_list::iterator p = texts.begin();
3041    while (p != texts.end()) {
3042        double x,y;
3043        if (mytransform.polar_projection(p->rectascension,
3044                                         p->declination,
3045                                         x,y)) {
3046            p->in_view = visible;
3047            p->x = x;
3048            p->y = y;
3049            objects.push_back(&(*p));
3050        }
3051        p++;
3052    }
3053}
3054
3055@*1 Flex labels.  ``Flex labels'' are drawn along a path that needn't (and
3056usually isn't) be a straight line.  Unfortunately, due to this, their
3057dimensions are very difficult to predict and therefore they are {\it not\/}
3058part of the penalty algorithm -- at least not in the current version.
3059
3060Eventually \PPTHREE/ could have many kinds of flexes, therefore there is a
3061common (abstract) ancestor for all of them called |flex_label|.  But at the
3062moment I only implement the by far most important one, the |declination_flex|,
3063see below.
3064
3065Flexes are special also in another respect: They are read from the input
3066script.  This means that the |flexes_list| that contains all flexes (of all
3067kinds) is filled during reading the input script.  Therefore there is no {\it
3068read\_flexes\/}(\,), and no file format associated with it.
3069
3070@s flex_label int
3071@s declination_flex int
3072
3073@q')}@>
3074
3075@c
3076struct flex_label : public view_data {
3077    double rectascension, declination;
3078    flex_label(string s, double r, double d, color c, int a, bool b);
3079    virtual bool draw(const transformation& mytransform,
3080                      dimensions_list& dimensions, objects_list& objects)
3081        const = 0;
3082};
3083
3084@ Of course I need no extra label because a flex is a label of its own.  So a
3085flex only uses |label| and |label_angle|.  The rest is unimportant because the
3086label isn't printed anyway because the coodrinates |x| and |y| are invalid
3087anyway and therefore printing will be rejected in |print_labels()|.
3088
3089Actually |on_baseline| is insignificant for flexes.
3090
3091@c
3092flex_label::flex_label(string s, double r, double d, color c, int a, bool b)
3093    : rectascension(r), declination(d) {
3094    label_color = c;
3095    label = string("\\FlexLabel{") + s + '}';
3096    label_angle = a;
3097    on_baseline = b;
3098    in_view = visible;
3099    with_label = hidden;
3100    label_arranged = true;
3101}
3102
3103typedef list<flex_label*> flexes_list;
3104
3105@ |declination_flex| enables us to write a text along a path of constant
3106declination.  This looks very nice on the chart.
3107
3108@c
3109struct declination_flex : public flex_label {
3110    declination_flex(string s, double r, double d, color c, int a, bool b)
3111        : flex_label(s,r,d,c,a,b) { }
3112    virtual bool draw(const transformation& mytransform,
3113                      dimensions_list& dimensions, objects_list& objects)
3114        const;
3115    virtual double penalties_with(const double left, const double right,
3116                                  const double top, const double bottom,
3117                                  bool core = true) const;
3118};
3119
3120@ This is the main declination flex routine.  I calculate the dimensions of the
3121label in order to find out how long the path must be and how much it must be
3122shifted vertically in order to get the desired angular positioning.  Both
3123values can only be estimates.  (For example, the scale is not constant on the
3124map, so it cannot work perfectly.  However I try to assure that the path will
3125be too long rather than too short.)
3126
3127FixMe: This routine should take distortions due to the used map projection into
3128account.
3129
3130Then I calculate the coordinates of three points that form the path.  Start
3131point, end point, and one exactly in between.  Of course all have the same
3132declination.  |lower| is measured in em and denotes the amount by which the
3133whole text is shifted downwards.  This is sub-optimal because it may affect
3134letterspacing disadvantageously.
3135
3136Finally the three coordinate pairs and the label text are sent to the output in
3137form of a \PS/Tricks text path command.
3138
3139@c
3140bool declination_flex::draw(const transformation& mytransform,
3141                            dimensions_list& dimensions,
3142                            objects_list& objects) const {
3143    if (dimensions.find(label) == dimensions.end())
3144        recalculate_dimensions(dimensions, objects);
3145    if (dimensions.find(label) == dimensions.end())
3146        throw string("Update of label dimensions file failed: \"") +
3147            label + "\" not found";
3148    const double label_width = dimensions[label].width;
3149    const double label_height = dimensions[label].height;
3150    const double rectascension_halfwidth = label_width
3151        * mytransform.get_rad_per_cm() * 180.0/M_PI / 15.0
3152        / cos(declination * M_PI/180.0) / 2.0;
3153    char justification;
3154    double path_point_rectascension[3];
3155    path_point_rectascension[0] = rectascension;
3156    path_point_rectascension[1] = rectascension - rectascension_halfwidth;
3157    path_point_rectascension[2] = rectascension -
3158        2.0 * rectascension_halfwidth;
3159    switch (label_angle) {
3160    case 0: case 1: case 7:
3161        justification = 'l';
3162        break;
3163    case 2: case 6:
3164        justification = 'c';
3165        for (int i = 0; i < 3; i++)
3166            path_point_rectascension[i] += rectascension_halfwidth;
3167        break;
3168    case 3: case 4: case 5:
3169        justification = 'r';
3170        for (int i = 0; i < 3; i++)
3171            path_point_rectascension[i] += 2.0 * rectascension_halfwidth;
3172        break;
3173    }
3174    double lower;
3175    switch (label_angle) {
3176    case 0: case 4: lower = -0.4; break;
3177    case 1: case 2: case 3: lower = 0.0; break;
3178    case 5: case 6: case 7: lower = -0.8; break;
3179    }
3180    double x[3],y[3];
3181    for (int i = 0; i < 3; i++)
3182        if (!mytransform.polar_projection(path_point_rectascension[i],
3183                                          declination, x[i],y[i]))
3184            return false;
3185
3186    label_color.set(OUT);
3187    OUT << "\\Label{\\pstextpath[" << justification
3188        << "](0," << lower << "em){\\pscurve[linestyle=none]%\n  ";
3189    for (int i = 0; i < 3; i++)
3190        OUT << '(' << x[i] << "cm," << y[i] << "cm)";
3191    OUT << "}{\\dummycolor" << label << "}}%\n";
3192
3193    return true;
3194}
3195
3196@ As I've already said, a flex is (at the moment) excluded from the penalty
3197algorithm.  This is realised here: This routine always returns zero.
3198
3199@c
3200double declination_flex::penalties_with(const double left, const double right,
3201                   const double top, const double bottom,
3202                   bool core) const {
3203        return 0.0;
3204}
3205
3206@ This is the drawing routine called from the main program.  It goes through
3207all flexes twice: First it only fills |objects|.  Then it actually draws the
3208flexes.  The reason is efficiency: This assures that all needed labels are
3209available before the first flex is about to be draw which may mean a
3210reclaculation of the label dimensions.  If I realised this in one pass, the
3211needed labels would occure over and over again, and every time it would be
3212necessary to recalculate the dimensions.
3213
3214@c
3215void draw_flex_labels(const transformation& mytransform,
3216                      const flexes_list& flexes, objects_list& objects,
3217                      dimensions_list& dimensions) {
3218    flexes_list::const_iterator p = flexes.begin();
3219    while (p != flexes.end()) objects.push_back(*p++);
3220    p = flexes.begin();
3221    while (p != flexes.end()) (*p++)->draw(mytransform,dimensions,objects);
3222}
3223
3224@* Drawing stars.  Stars are a little bit simpler than nebulae because they are
3225mere disks.  They are only included if they have a certain minimal magnitude.
3226The disk radius is calculated according to $$\eqalign{ \hbox{|radius|} &=
3227\sqrt{m_{\hbox{\sevenrm min}} - m + \hbox{\it radius}_{\hbox{\sevenrm
3228min}}^2}\,,\quad\hbox{if $m<m_{\hbox{\sevenrm min}}$\,,}\cr
3229\noalign{\vskip0.5ex} \hbox{|radius|} &= m_{\hbox{\sevenrm min}}\,,\quad
3230\hbox{otherwise}.}$$
3231
3232The star gets a label by default only if it has a certain magnitude.  This is
3233even a little bit stricter than the related condition above.
3234
3235Then only the stellar colour has yet to be calculated, and it can be printed.
3236
3237@q;@>
3238
3239@c
3240@<|create_hs_colour()| for star colour determination@>@;@#
3241
3242void draw_stars(const transformation& mytransform, stars_list& stars,
3243                objects_list& objects) {
3244    for (int i = 0; i < stars.size(); i++)
3245        if (stars[i].in_view != hidden &&
3246            stars[i].magnitude < params.faintest_star_magnitude) {
3247                // Effectively all stars of the \BSC/
3248            if (mytransform.polar_projection(stars[i].rectascension,
3249                                             stars[i].declination,
3250                                             stars[i].x,stars[i].y)) {
3251                stars[i].in_view = visible;
3252                const double m_dot = params.faintest_star_disk_magnitude;
3253                const double r_min = params.minimal_star_radius /
3254                    params.star_scaling;
3255                stars[i].radius = params.star_scaling *
3256                    (stars[i].magnitude < m_dot ?
3257                     sqrt((m_dot - stars[i].magnitude)/300.0 + r_min*r_min)
3258                     : r_min);
3259                if (stars[i].with_label == undecided)
3260                    stars[i].with_label =
3261                        (stars[i].magnitude <
3262                         params.faintest_star_with_label_magnitude &&
3263                         !stars[i].name.empty()) ? visible : hidden;
3264                if (params.colored_stars) {
3265                    OUT << "\\newhsbcolor{starcolor}{";
3266                    create_hs_colour(stars[i].b_v,stars[i].spectral_class);
3267                    OUT << " 1}%\n";
3268                } else OUT << params.starcolor;
3269                OUT << "\\pscircle*[linecolor=starcolor]("
3270                    << stars[i].x << ","
3271                    << stars[i].y << "){"
3272                    << stars[i].radius / 2.54 * 72.27 << "pt}%\n";
3273                objects.push_back(&stars[i]);
3274            } else stars[i].in_view = hidden;
3275        } else stars[i].in_view = stars[i].with_label = hidden;
3276}
3277
3278@ I want to use the B$-$V magnitude for the colour of the star disks on the
3279maps.  Here I map the value of the B$-$V magnitude to a colour in the \HSB/
3280space.  `\HSB/' -- `Hue, Saturation, Brightness' (all three fom $0$ to~$1$).
3281Brightness is always~$1$, so only hue and saturation have to be calculated.
3282
3283There are three intervals for B$-$V with the boundaries |bv0|, |bv1|, |bv2|,
3284and |bv3|.  |bv0|--|bv1| is blue, |bv1|--|bv2| is white, and |bv2|--|bv3| is
3285red.  On each boundary, the hue values |hue0|--|hue3| respectively are valid.
3286Inbetween I interpolate linearly (rule of three).
3287
3288@q;@>
3289
3290@<|create_hs_colour()| for star colour determination@>=
3291void create_hs_colour(double b_v, string spectral_class) {
3292    double hue, saturation;
3293    const double bv0 = -0.1, bv1 = 0.001, bv2 = 0.62, bv3 = 1.7;
3294    const double hue0 = 0.6, hue1 = 0.47, hue2 = 0.17, hue3 = 0.0;
3295    const double min_saturation = 0.0, max_saturation = 0.2;
3296    @<Handle missing B$-$V value@>@;
3297    if (b_v < bv0) b_v = bv0;  // cut off extreme values
3298    if (b_v > bv3) b_v = bv3;
3299    if (b_v < bv1) {  // blue star
3300        hue = (b_v - bv0) / (bv1 - bv0)
3301            * (hue1 - hue0) + hue0;
3302        saturation = (b_v - bv0) / (bv1 - bv0)
3303            * (min_saturation - max_saturation) + max_saturation;
3304    }
3305    else if (b_v < bv2) { // white star: constantly white.
3306        hue = 0.3;  // could be anything
3307        saturation = 0;
3308    }
3309    else {  // red star
3310        hue =  (b_v - bv2) / (bv3 - bv2)
3311            * (hue3 - hue2) + hue2;
3312        saturation = (b_v - bv2) / (bv3 - bv2)
3313            * (max_saturation - min_saturation) + min_saturation;
3314    }
3315    OUT << hue << ' ' << saturation;
3316}
3317
3318@ Since there are some stars in the stellar catalogue without a B$-$V
3319brightness, I need a fallback on the spectral class.  For such stars is
3320$\hbox{|b_v|} = 99$.  In this routine I use the very first character in the
3321string with the spectral class for determining an estimated value for
3322B$-$V\spacefactor1000.  The values are averages of all stars in the \BSC/ with
3323the respective spectral class.
3324
3325@<Handle missing B$-$V value@>=
3326    if (b_v > 90.0) {
3327        switch (spectral_class[0]) {
3328        case 'O': b_v = 0.0; @+ break;
3329        case 'B': b_v = -0.07; @+ break;
3330        case 'A': b_v = 0.11; @+ break;
3331        case 'F': b_v = 0.43; @+ break;
3332        case 'G': b_v = 0.89; @+ break;
3333        case 'K': b_v = 1.24; @+ break;
3334        case 'M': b_v = 1.62; @+ break;
3335        case 'N': b_v = 2.88; @+ break;
3336        case 'S': b_v = 1.84; @+ break;
3337        case 'C': b_v = 3.02; @+ break;
3338        default: b_v = 0.0; @+ break;
3339        }
3340    }
3341
3342
3343@* Drawing nebulae.  Only nebulae with a certain minimal brightness are
3344included, and all Messier objects, but all of these get a label by default.
3345
3346The first decision I have to make here is whether the nebula has all necessary
3347data for drawing a neat ellipsis that has the correct diameters and the correct
3348angle.  If this is not anvailable (|horizontal_angle|${}=720^\circ$), the
3349nebula ellipsis is re-calculated so that $$\eqalign{ \hbox{|diameter_x|}_
3350{\hbox{\sevenrm new}} &= \hbox{|diameter_y|}_ {\hbox{\sevenrm
3351new}}\quad\hbox{and}\cr \noalign{\vskip0.5ex} \hbox{|diameter_x|}_
3352{\hbox{\sevenrm new}} \cdot \hbox{|diameter_y|}_ {\hbox{\sevenrm new}} &=
3353\hbox{|diameter_x|}_ {\hbox{\sevenrm old}} \cdot \hbox{|diameter_y|}_
3354{\hbox{\sevenrm old}}}$$ (make the ellipsis a circle of the same area) which
3355means $$\hbox{|diameter_x|}_ {\hbox{\sevenrm new}} \mathrel{:=}
3356\hbox{|diameter_y|}_ {\hbox{\sevenrm new}} \mathrel{:=}
3357\sqrt{\hbox{|diameter_x|}_ {\hbox{\sevenrm old}} \cdot \hbox{|diameter_y|}_
3358{\hbox{\sevenrm old}}}.$$
3359
3360The |radius| of the nebula is a rough estimate: It is simply the half of
3361|diameter_x|.  If the nebula is too small, it is printed as a minimal circle.
3362If it's large enough, it is printed in its (almost) full beauty, see |@<Draw
3363nebula shape@>|.
3364
3365@q'@>
3366
3367@c
3368void draw_nebulae(const transformation& mytransform, nebulae_list& nebulae,
3369                  objects_list& objects) {
3370    OUT << "\\psset{linecolor=nebulacolor,linewidth="
3371        << params.linewidth_nebula << "cm,linestyle="
3372        << params.linestyle_nebula << ",curvature=1 .5 -1}%\n";
3373    for (int i = 0; i < nebulae.size(); i++)
3374        if (nebulae[i].in_view == visible ||
3375            (nebulae[i].in_view == undecided &&
3376             (((nebulae[i].kind == open_cluster ||
3377                nebulae[i].kind == globular_cluster)
3378               && nebulae[i].magnitude < params.faintest_cluster_magnitude)
3379             || @/
3380              ((nebulae[i].kind == galaxy || nebulae[i].kind == reflection ||
3381                nebulae[i].kind == emission) &&
3382               nebulae[i].magnitude < params.faintest_diffuse_nebula_magnitude)
3383             ||
3384              nebulae[i].messier > 0 ))) {
3385            if (mytransform.polar_projection(nebulae[i].rectascension,
3386                                             nebulae[i].declination,
3387                                             nebulae[i].x,nebulae[i].y)) {
3388                nebulae[i].in_view = visible;
3389                if (nebulae[i].horizontal_angle > 360.0)
3390                    nebulae[i].diameter_x = nebulae[i].diameter_y =
3391                        sqrt(nebulae[i].diameter_x * nebulae[i].diameter_y);
3392                nebulae[i].radius = nebulae[i].diameter_x/2.0 /
3393                    mytransform.get_rad_per_cm() * M_PI / 180.0;
3394                if (nebulae[i].with_label != hidden)
3395                    nebulae[i].with_label = visible;
3396                if (nebulae[i].radius > params.minimal_nebula_radius) {
3397                    @<Draw nebula shape@>@;
3398                } else {
3399                    nebulae[i].radius = params.minimal_nebula_radius;
3400                    OUT << "\\pscircle("
3401                        << nebulae[i].x << ","
3402                        << nebulae[i].y << "){"
3403                        << nebulae[i].radius / 2.54 * 72.27 << "pt}%\n";
3404                }
3405                objects.push_back(&nebulae[i]);
3406            } else nebulae[i].in_view = hidden;
3407        } else nebulae[i].in_view = nebulae[i].with_label = hidden;
3408}
3409
3410@ This is the core of |draw_nebula()|.  In order to draw the (almost) ellipsis,
3411I define four reference points at the vertexes of the ellipsis.  In the loop
3412they are then transformed to screen coordinates and printed.
3413
3414Mathematically, the algorithm used here works only for infitesimally small
3415nebulae on the equator.  The problem of ``finding a point that is $x$ degrees
3416left from the current point with an angle of $\alpha$ degrees'' is actually
3417much more difficult.  This is also the reason for this special case
3418|nebulae[i]|\hskip0pt|@[.diameter_x@] == nebulae[i]|\hskip0pt|@[.diameter_y@]|.
3419It shouldn't be necessary, and at the rim of the view frame it's even wrong due
3420to the different circular scale.  FixMe: Improve this. (Via rotation matrices.)
3421
3422@q;@>
3423
3424@<Draw nebula shape@>=
3425    if (nebulae[i].diameter_x == nebulae[i].diameter_y)
3426        OUT << "\\pscircle(" << nebulae[i].x << ',' << nebulae[i].y
3427            << "){" << nebulae[i].radius << "}%\n";
3428    else {
3429        double rectascension[4], declination[4];
3430        const double r_scale = 1.0 / cos(nebulae[i].declination * M_PI/180.0);
3431        const double cos_angle
3432            = cos(nebulae[i].horizontal_angle * M_PI/180.0);
3433        const double sin_angle
3434            = sin(nebulae[i].horizontal_angle * M_PI/180.0);
3435        const double half_x = nebulae[i].diameter_x/2.0;
3436        const double half_y = nebulae[i].diameter_y/2.0;
3437        rectascension[0] = nebulae[i].rectascension -
3438            half_x * cos_angle / 15.0 * r_scale;
3439        declination[0] = nebulae[i].declination -
3440            half_x * sin_angle;
3441        rectascension[1] = nebulae[i].rectascension +
3442            half_y * sin_angle / 15.0 * r_scale;
3443        declination[1] = nebulae[i].declination -
3444            half_y * cos_angle;
3445        rectascension[2] = nebulae[i].rectascension +
3446            half_x * cos_angle / 15.0 * r_scale;
3447        declination[2] = nebulae[i].declination +
3448            half_x * sin_angle;
3449        rectascension[3] = nebulae[i].rectascension -
3450            half_y * sin_angle / 15.0 * r_scale;
3451        declination[3] = nebulae[i].declination +
3452            half_y * cos_angle;
3453        OUT << "\\psccurve";
3454        for (int j = 0; j < 4; j++) {
3455            double x,y;
3456            mytransform.polar_projection(rectascension[j],
3457                                         declination[j], x, y);
3458            OUT << '(' << x << ',' << y << ')';
3459        }
3460    }
3461    OUT << "\\relax\n";
3462
3463
3464@* Grid and other curves.  It's boring to have only stars on the map.  I want
3465to have the usual coordinate grid with rectascension and declination lines,
3466plus the ecliptic and maybe the galactic equator, plus the constellation
3467borders and constellation lines.  This is done here.
3468
3469@ The first routine is basic for all the following: It
3470helps to draw a smooth curve through the given points.  Actually it's a mere
3471code fragment that is used later several times, therefore it's declared
3472|inline| and it has unfortunately many parameters.
3473
3474|rectascension| and |declination| are the celestial coordinates of the point.
3475|i| is the number of the scan point in the current curve.  Later it'll be the
3476loop variable.  |within_curve| is |true| if I'm actually drawing a curve, and
3477|false| if the current segment is not visible.  |steps| is the number of |i|'s
3478that I skip over before I deploy a curve point.  (I {\it scan\/} much more
3479points than I actually {\it draw}, because the resulting curve is smoothed
3480anyway so a very high point density is not necessary.)
3481
3482|last_x| and |last_y| simply contain the values of |x| and |y| from the last
3483call, because I need them when I step outwards of the view frame: Then I must
3484draw a curve ending point at the {\it last\/} coordinates rather than the
3485current ones.
3486
3487|steps|${}=1$ denotes the ending point of a curve.
3488
3489FixMe: There is a very basic flaw here, namely that the curve directions in the
3490endpoints may be rather suboptimal.  This is particularly annoying when drawing
3491a circle-like shape.  However, a solution is not easy; so far the only help is
3492to make the points denser.
3493
3494@c
3495inline void add_curve_point(const double rectascension,
3496                            const double declination,
3497                            const transformation& transform,
3498                            const int i, bool& within_curve,
3499                            const int steps) {
3500    static double last_x, last_y;
3501    double x,y;
3502    if (transform.polar_projection(rectascension, declination, x, y)) {
3503        if (!within_curve) {  // start a new one
3504            OUT << "\\pscurve" << "(" << x << ',' << y << ")";
3505            within_curve = true;
3506        } else if (i % steps == 0) OUT << "(" << x << ',' << y << ")";
3507        if (i % (steps*4) == 0 || steps == 1) OUT << "%\n";
3508            /* line break every four coordinates */
3509    } else
3510        if (within_curve) { // end the current curve
3511            OUT << "(" << last_x << ',' << last_y << ")" << "\\relax\n";
3512            within_curve = false;
3513        }
3514    last_x = x;  last_y = y;
3515}
3516
3517@ This is the principal grid routine that is called from the |main()| function.
3518|scans_per_cm| is the density of tests whether we are within the view frame or
3519not.  By default, we scan once every millimetre.  |point_distance| is given in
3520degrees and it's the distance between two actually drawn curve points.
3521
3522|scans_per_fullcircle| is the number of scan points in one full arc.  It is
3523used in the following code for determining the number of loop repetitions.  In
3524order to keep the meaning of all quantities, the grid creating code should
3525respect this variable.  E.\,g., most declination circles are smaller than one
3526full arc.  Therefore they mustn't use the full value of
3527|scans_per_fullcircle|.
3528
3529|steps| and |within_curve| should be clear from the routine
3530 |add_curve_point()|.  |steps| is at least $2$, because it must be at least $1$
3531 anyway and ``$1$'' has a special meaning in |add_curve_point|: It denotes the
3532 end of a curve. |within_curve| must be reset to |false| if a new set of lines
3533 is to be drawn.
3534
3535For a plot with closed lines (e.\,g.\ of one of the poles), you may set
3536|point_distance| to |0| and |scans_per_cm| to a higher value, for avoiding a
3537kink at the joint.
3538
3539@c
3540void create_grid(const transformation transform,
3541                 const double scans_per_cm = 10,
3542                 const double point_distance = 5.0) {
3543    if (!params.show_grid && !params.show_ecliptic) return;
3544    const double scans_per_fullcircle =
3545        scans_per_cm / transform.get_rad_per_cm() * 2.0*M_PI;
3546    const int steps = int((point_distance * M_PI/180.0) *
3547                          (scans_per_fullcircle/(2.0*M_PI))) + 2;
3548    bool within_curve;
3549    if (params.show_grid) {
3550        OUT << "\\psset{linestyle=" << params.linestyle_grid
3551            << ",linecolor=gridcolor,linewidth="
3552            << params.linewidth_grid << "cm}%\n";
3553        @<Create grid lines for equal declination@>@;
3554        @<Create grid lines for equal rectascension@>@;
3555    }
3556    if (params.show_ecliptic) {
3557        @<Draw the ecliptic@>@;
3558    }
3559}
3560
3561@ As mentioned before, declination circles are smaller than the full circle of
3562the celestial sphere.  Therefore I reduce the |scans_per_fullcircle| by
3563$cos(\hbox{|declination|})$ in order to decrese the number of scan points.  The
3564equator is drawn with a slightly bigger line width.
3565
3566This strange construction with ``|i==number_of_points?1:steps|'' is necessary
3567because the very last point {\it must\/} be drawn.
3568
3569@<Create grid lines for equal declination@>=
3570    for (int declination = -80; declination <= 80; declination += 10) {
3571        if (declination == 0)
3572            OUT << "\\psset{linewidth=" << 2.0 * params.linewidth_grid
3573                << "cm}%\n";
3574        within_curve = false;
3575        const int number_of_points =
3576            int(cos(declination*M_PI/180.0)*scans_per_fullcircle);
3577        for (int i = 0; i <= number_of_points; i++)
3578            add_curve_point(double(i)/double(number_of_points)*24.0,
3579                            declination,transform,i,within_curve,
3580                            i==number_of_points?1:steps);
3581        if (declination == 0)
3582            OUT << "\\psset{linewidth=" << params.linewidth_grid << "cm}%\n";
3583    }
3584
3585@ The only slightly interesting thing here is that I draw the lines of equal
3586rectascension only from $-80^\circ$ to $+80^\circ$ of declination, because
3587otherwise it gets too populated in the pole regions.
3588
3589@<Create grid lines for equal rectascension@>=
3590    const int number_of_points = int(scans_per_fullcircle / 2.0);
3591    for (int rectascension = 0; rectascension <= 23; rectascension++) {
3592        within_curve = false;
3593        for (int i = 0; i <= number_of_points; i++)
3594            add_curve_point(double(rectascension),
3595                            double(i)/double(number_of_points)*160.0 - 80.0,
3596                            transform,i,within_curve,
3597                            i==number_of_points?1:steps);
3598    }
3599
3600@ Unfortunately the naive approach for drawing the ecliptic,
3601$$\hbox{|declination|} = \varepsilon \cdot \sin(\hbox{|rectascension|})$$ (with
3602$\varepsilon$ being the angle between equator and ecliptic) is wrong.  So I
3603have to take the equatorial declination circle, transform it into cartesian
3604coordinates, apply a simple rotation matrix for turning it into the ecliptical
3605circle, and transform it back into celestial coordinates.  Fortunately the
3606ecliptic lies very symmetrically, so the resulting formulae are rather simple:
3607
3608Being $\varphi_0$ the right ascension angle ($=\hbox{\it rectascension} \cdot
360915^\circ\!/\hbox{h}$) of the point on the equator, its right ascension
3610$\varphi$ and declination $\delta$ after becoming the ecliptic are
3611$$\eqalign{\varphi &= \arctan_2\frac{-\sin\varphi_0
3612\cos\varepsilon}{\cos\varphi_0}\cr\delta &= \arcsin(-\sin\varphi_0
3613\sin\varepsilon).}$$  $\arctan_2$ is the quadrant-aware version of $\arctan$,
3614because $\arctan$ only returns values between $-\pi/2$ and $+\pi/2$.
3615
3616
3617@<Draw the ecliptic@>=
3618    OUT << "\\psset{linestyle=" << params.linestyle_ecliptic
3619        << ",linecolor=eclipticcolor,"
3620        << "linewidth=" << params.linewidth_ecliptic << "cm}%\n";
3621    {
3622        const double epsilon = 23.44 * M_PI / 180.0;
3623        const int number_of_points = int(scans_per_fullcircle);
3624        within_curve = false;
3625        for (int i = 0; i <= number_of_points; i++) {
3626            const double phi0 = double(i)/double(number_of_points)*2.0*M_PI;
3627            const double m_sin_phi0 = -sin(phi0);
3628            const double phi = atan2(m_sin_phi0 * cos(epsilon),cos(phi0));
3629            const double delta = asin(m_sin_phi0*sin(epsilon));
3630            add_curve_point(phi * 12.0 / M_PI, delta * 180.0 / M_PI,
3631                            transform,i,within_curve,
3632                            i==number_of_points?1:steps);
3633        }
3634    }
3635
3636@*1 Constellation boundaries.  They are drawn at a quite early stage so that
3637they are overprinted by more interesting stuff.
3638
3639@ This routine is a helper and is used in |draw_constellation_boundaries()|.
3640It draws one boundary line~|b|, but only the part that is visible.  For this in
3641the first part of this routine, the container |current_line| is filled with all
3642points of |b| that are actually visible, with their screen coordinates in
3643centimetres.
3644
3645If |current_line| consists only of a start and an end point, a `\.{\\psline}'
3646is created which is a straight line.  I cannot get a curvature from anywhere
3647easily in this case.  Otherwise it is a `\.{\\pscurve}' which means that we go
3648smoothly through all points.
3649
3650I need this |"[liftpen=2]"| because eventually the \.{\\pscurve} command may be
3651part of a \.{\\pscustom} command and thus be part of a bigger path that forms
3652-- in terms of the Porstscript language -- one united path.\footnote{$^2$}{This
3653means e.\,g.\ that a dashed line pattern won't @q'@> be broken at subpath
3654junctions.}  In order to get crisp coners, the \.{liftpen} option is necessary.
3655
3656@c
3657void draw_boundary_line(const boundary& b, const transformation& transform,
3658                        objects_list& objects, bool highlighted = false) {
3659    vector<point> current_line;
3660    for (int j = 0; j < b.points.size(); j++) {
3661        const double rectascension = b.points[j].x;
3662        const double declination = b.points[j].y;
3663        double x,y;
3664        if (!transform.polar_projection(rectascension,declination,x,y))
3665            continue;
3666        current_line.push_back(point(x,y));
3667    }
3668    if (current_line.size() >= 2) {
3669        if (current_line.size() == 2) OUT << "\\psline";
3670        else OUT << "\\pscurve";
3671        OUT << "[liftpen=2]{c-c}";
3672        for (int j = 0; j < current_line.size(); j++) {
3673            OUT << '(' << current_line[j].x << ','
3674                 << current_line[j].y << ')';
3675            if (j % 4 == 3) OUT << "%\n";
3676            if (highlighted)
3677                @<Create a |boundary_atom| for the |objects|@>@;
3678        }
3679        OUT << "\\relax\n";
3680    }
3681}
3682
3683@ This is the point where the |boundary_atom|s are actually created.  As one
3684can see, it's very simple.  I just draw a virtual line from the current point
3685in the current path to the previous one.  Unfortunately I create new objects
3686here on the heap that are never deleted.  But it's harmless nevertheless.
3687
3688@<Create a |boundary_atom| for the |objects|@>=
3689if (j > 0) objects.push_back(new boundary_atom(current_line[j],
3690                                               current_line[j-1]));
3691
3692@ This is the routine that is actually called from the main program.  There are
3693two possibilities: Either the string |constellation| is empty or not.  If not,
3694the caller wants to highlight the given constellation.  Then we have to
3695undertake a two step process: \medskip
3696
3697\item{(1)} Draw all boundaries that do not belong to |constellation|.
3698\item{(2)} Draw all boundaries that enclose |constellation| in a hightlighted
3699style and {\it as one path\/} via \.{\\pscustom}.
3700
3701\medskip If no |constellation| is given we simply draw all lines in
3702|boundaries| in modus~(1).
3703
3704@c
3705void draw_boundaries(const transformation& mytransform,
3706                     boundaries_list& boundaries,
3707                     objects_list& objects,
3708                     string constellation = string("")) {
3709    OUT << "\\psset{linecolor=boundarycolor,linewidth="
3710        << params.linewidth_boundary << "cm,"
3711        << "linestyle=" << params.linestyle_boundary << "}%\n";
3712    if (!constellation.empty()) {
3713        for (int i = 0; i < boundaries.size(); i++)
3714            if (!boundaries[i].belongs_to_constellation(constellation))
3715                draw_boundary_line(boundaries[i], mytransform, objects);
3716        OUT << "\\psset{linecolor=hlboundarycolor,linewidth="
3717            << params.linewidth_boundary << "cm," << "linestyle="
3718            << params.linestyle_hlboundary << "}%\n";
3719        OUT << "\\pscustom{";
3720        for (int i = 0; i < boundaries.size(); i++)
3721            if (boundaries[i].belongs_to_constellation(constellation))
3722                draw_boundary_line(boundaries[i], mytransform, objects, true);
3723        OUT << "}%\n";
3724    } else
3725        for (int i = 0; i < boundaries.size(); i++)
3726            draw_boundary_line(boundaries[i], mytransform, objects);
3727}
3728
3729@*1 Constellation lines.  In the loop I test all lines available in
3730|connections|.  The first thing in the loop is to assure that at least one of
3731stars is actually visible.  At the beginning $(\hbox{|x1|}, \hbox{|y1|})$ and
3732$(\hbox{|x2|}, \hbox{|y2|})$ are the screen coordinates of the two stars that
3733are supposed to be connected by a line.  Then I move from there to the
3734respective other star by the amount of |skip|.  The current length of the
3735connection line is stored in~|r| which must have a minimal value (in particular
3736it must be positive), otherwise it doesn't make sense to draw the line.
3737Finally the line is drawn from the new $(\hbox{|x1|}, \hbox{|y1|})$ to the new
3738$(\hbox{|x2|}, \hbox{|y2|})$.
3739
3740I expect |has_valid_coordinates()| to be |false| if the star is on the
3741non-visible hemisphere (i.\,e.\ $z<0$).
3742
3743
3744@q'@>
3745
3746@c
3747void draw_constellation_lines(connections_list& connections,
3748                              const stars_list& stars,
3749                              objects_list& objects) {
3750    OUT << "\\psset{linecolor=clinecolor,linestyle=" << params.linestyle_cline
3751        << ",linewidth=" << params.linewidth_cline << "cm}%\n";
3752    for (int i = 0; i < connections.size(); i++)
3753        if ((stars[connections[i].from].in_view == visible ||
3754            stars[connections[i].to].in_view == visible)
3755            && stars[connections[i].from].has_valid_coordinates()
3756            && stars[connections[i].to].has_valid_coordinates()) {
3757            double x1 = stars[connections[i].from].x;
3758            double y1 = stars[connections[i].from].y;
3759            double x2 = stars[connections[i].to].x;
3760            double y2 = stars[connections[i].to].y;
3761            const double phi = atan2(y2 - y1, x2 - x1);
3762            double r = hypot(x2 - x1, y2 - y1);
3763            double skip;
3764            skip = stars[connections[i].from].radius +
3765                stars[connections[i].from].skip;
3766            r -= skip;
3767            x1 += skip * cos(phi);
3768            y1 += skip * sin(phi);
3769            skip = stars[connections[i].to].radius +
3770                stars[connections[i].to].skip;
3771            r -= skip;
3772            x2 -= skip * cos(phi);
3773            y2 -= skip * sin(phi);
3774            connections[i].radius = r/2.0;
3775            connections[i].x = (x1 + x2) / 2.0;
3776            connections[i].y = (y1 + y2) / 2.0;
3777            if (r > params.shortest_constellation_line && r > 0.0) {
3778                OUT << "\\psline{cc-cc}(" << x1 << ',' << y1
3779                    << ")(" << x2 << ',' << y2 << ")%\n";
3780                connections[i].start = point(x1,y1);
3781                connections[i].end = point(x2,y2);
3782                objects.push_back(&connections[i]);
3783            }
3784        }
3785
3786}
3787
3788
3789@* Drawing the Milky Way.  If a proper data file is available, the milky way is
3790a simple concept, however difficult to digest for \LaTeX\ due to many many
3791Postscipt objects.  But for this program it's so simple that I can do the
3792reading and drawing in one small routine and I even don't need any large data
3793structures.
3794
3795FixMe: Actually, under very adverse circumstances, it is possible that gaps
3796occur between the pixels.  This is when polar regions (that fully use the
3797maximal pixel distance with \PPTHREE/'s original Milky Way data) lie on the rim
3798of a full hemisphere plot.  This doesn't do much harm though.
3799
3800FixMe: Additionally, this routine should take into account a)~that the original
3801Milky Way data was in azimuthal equidistant projection and b)~the current map
3802projection.  Both should result in optimised radii which would solve the above
3803problem too.
3804
3805See |@<Reading the milkyway into |pixels|@>| for more information on |pixels|.
3806
3807@c
3808void draw_milky_way(const transformation& mytransform) {
3809    vector<vector<point> > pixels(256);
3810    @<Reading the milkyway into |pixels|@>@;
3811    for (int i = 1; i < pixels.size(); i++) {
3812        if (pixels[i].size() == 0) continue;
3813        interpolate_colors(double(i) / 255.0, params.bgcolor,
3814                           params.milkywaycolor).set(OUT);
3815        for (int j = 0; j < pixels[i].size(); j++)
3816            OUT << "\\qdisk(" << pixels[i][j].x << "," << pixels[i][j].y
3817                << "){" << radius << "pt}%\n";
3818    }
3819}
3820
3821
3822@** The main function.  This consists of six parts: \medskip
3823
3824\item{(1)} Command line interpretation.
3825\item{(2)} Definition of the desired transformation, here called |mytransform|.
3826\item{(3)} Definition of the containers and
3827\item{(4)} the filling of them by reading from text files.
3828\item{(5)} Creating of the \LaTeX\ file, first and foremost by calling the
3829drawing routines.
3830\item{(6)} Possibly create an \EPS/ or \PDF/ file by calling the necessary
3831programs.
3832
3833\medskip\noindent That's it.
3834
3835@q'@>
3836
3837@c
3838@<Routines for reading the input script@>@#@;
3839
3840int main(int argc, char **argv) {
3841    try {
3842        @<Dealing with command line arguments@>@;
3843        read_parameters_from_script(*params.in);
3844        if (!params.filename_output.empty())
3845            params.out = new ofstream(params.filename_output.c_str());
3846        transformation mytransform(params.center_rectascension,
3847                                   params.center_declination,
3848                                   params.view_frame_width,
3849                                   params.view_frame_height,
3850                                   params.grad_per_cm);
3851
3852        @<Definition and filling of the containers@>@;
3853
3854        read_objects_and_labels(*params.in, dimensions, objects, stars,
3855                                nebulae, texts, flexes, mytransform);
3856
3857        OUT.setf(ios::fixed);  // otherwise \LaTeX\ gets confused
3858        OUT.precision(3);
3859        @<Create \LaTeX\ header@>@;
3860        OUT << "\\psclip{\\psframe[linestyle=none](0bp,0bp)("
3861            << params.view_frame_width_in_bp()
3862            << "bp," << params.view_frame_height_in_bp() << "bp)}%\n"
3863            << "\\psframe*[linecolor=bgcolor,linestyle=none](-1bp,-1bp)("
3864            << params.view_frame_width_in_bp() + 1
3865            << "bp," << params.view_frame_height_in_bp() + 1 << "bp)%\n";
3866        @<Draw all celestial objects and labels@>@;
3867        OUT << "\\endpsclip\n";
3868        @<Create \LaTeX\ footer@>@;
3869        if (params.input_file) delete params.in;
3870        if (!params.filename_output.empty())
3871            delete params.out; else OUT.flush();
3872        @<Create \EPS/ or \PDF/ file if requested@>@;
3873    }
3874    catch (string s) {
3875        cerr << "pp3: " << s << '.' << endl;
3876        exit(1);
3877    }
3878    return 0;
3879}
3880
3881@ \PPTHREE/ needs exactly one argument.  It must be either the file name of the
3882input script or a `\.{-}' which means that it takes standard input for reading
3883the input script.
3884
3885@q'@>
3886
3887@<Dealing with command line arguments@>=
3888        if (argc == 2) {
3889            if (argv[1][0] != '-') {
3890                params.in = new ifstream(argv[1]);
3891                if (!params.in->good())
3892                    throw string("Input script file ") + argv[1]
3893                        + " not found";
3894                else params.input_file = true;
3895            } else if (!strcmp(argv[1],"-")) params.in = &cin; else
3896                cerr << "Invalid argument: " << argv[1] << endl;
3897        }
3898        if (params.in == 0) {
3899            cerr << "PP3 1.3.2  Copyright (c) 2002--2004 Torsten Bronger\n" @/
3900                 << "           http://pp3.sourceforge.net\n\n" @/
3901                 << "Syntax:\n  pp3 {input-file}\n\n" @/
3902                 << "{input-file} may be \"-\" to denote standard input.\n" @/
3903                 << "You may give an empty file to get a default plot.\n" @/
3904                 << "The plot is sent to standard output by default.\n";
3905            exit(0);
3906        }
3907
3908@ I must define all containers, but of course I only read those data structures
3909that are actually used.
3910
3911@<Definition and filling of the containers@>=
3912        boundaries_list boundaries;
3913        dimensions_list dimensions;
3914        objects_list objects;
3915        stars_list stars;
3916        nebulae_list nebulae;
3917        connections_list connections;
3918        texts_list texts;
3919        flexes_list flexes;
3920
3921        read_stars(stars);  /* Must come first, because it tests whether
3922                               data files can be found */
3923        if (params.show_boundaries) read_constellation_boundaries(boundaries);
3924        read_label_dimensions(dimensions);
3925        if (params.nebulae_visible) read_nebulae(nebulae);
3926        if (params.show_lines) read_constellation_lines(connections, stars);
3927
3928@ Four calls here are not preceded by an |if| clause: |create_grid()| contains
3929such tests of its own (because it's divided into subsections), and in
3930|draw_flex_labels()|, |draw_text_labels()|, and |draw_stars()| every single
3931object is tested for visibility anyway.
3932
3933@q')@>
3934
3935@<Draw all celestial objects and labels@>=
3936        if (params.milkyway_visible) draw_milky_way(mytransform);
3937        create_grid(mytransform);
3938        if (params.show_boundaries)
3939            draw_boundaries(mytransform, boundaries, objects,
3940                            params.constellation);
3941        draw_flex_labels(mytransform, flexes, objects, dimensions);
3942        draw_text_labels(mytransform, texts, objects);
3943        if (params.nebulae_visible) draw_nebulae(mytransform, nebulae, objects);
3944        draw_stars(mytransform, stars, objects);
3945        if (params.show_lines)
3946            draw_constellation_lines(connections, stars, objects);
3947        if (params.show_labels) {
3948            arrange_labels(objects,dimensions);
3949            print_labels(objects);
3950        }
3951
3952@ This is the preamble and the beginning of the resulting \LaTeX\ file.  I use
3953the \.{geometry} package and a dvips \.{\\special} command to set the papersize
3954to the actual view frame plus 2~millimetres.  So I create a buffer border of
39551\,mm thickness.
3956
3957@<Create \LaTeX\ header@>=
3958    create_preamble(OUT);
3959    OUT << "\\usepackage[nohead,nofoot,margin=0cm," @/
3960        << "paperwidth=" << params.view_frame_width_in_bp() << "bp," @/
3961        << "paperheight=" << params.view_frame_height_in_bp() << "bp" @/
3962        << "]{geometry}\n\n" @/
3963        << "\n\\begin{document}\\parindent0pt\n" @/
3964        << "\\pagestyle{empty}\\thispagestyle{empty}%\n" @/
3965        << "\\special{papersize=" << params.view_frame_width_in_bp() - 0.1
3966        << "bp," << params.view_frame_height_in_bp() - 0.1 << "bp}%\n" @/
3967        << "\\vbox to " << params.view_frame_height_in_bp() << "bp{" @/
3968        << "\\vfill" @/
3969        << "\\hbox to " << params.view_frame_width_in_bp() << "bp{%\n" @/
3970        << params.bgcolor << params.gridcolor << params.eclipticcolor
3971        << params.boundarycolor << params.hlboundarycolor << params.starcolor
3972        << params.nebulacolor << params.clinecolor;
3973
3974@ If no preamble filename was given in the input script, a default preamble is
3975used.  If a preamble filename was given, the file should contain only font
3976specifying commands.  A possible preamble may be: \medskip
3977
3978{\parindent2em\baselineskip10.5pt\ninett\obeylines
3979\BS usepackage\LB eulervm\RB
3980\BS usepackage[T1]\LB fontenc\RB
3981\BS renewcommand*\LB \BS rmdefault\RB \LB pmy\RB \par}
3982
3983\medskip\noindent You may also have use for a command like \.{\BS
3984AtBeginDocument\LB\BS sffamily\RB} or \.{\BS boldmath} or \.{\BS large} or
3985whatever.  Or let be inspired by this fragment: \medskip
3986
3987{\parindent2em\baselineskip10.5pt\ninett
3988\BS usepackage\LB relsize\RB\par
3989\BS renewcommand*\LB\BS NGC\RB[1]\LB\BS smaller \#1\RB\par
3990\BS renewcommand*\LB\BS IC\RB[1]\LB\BS smaller\LB\BS smaller
3991     IC\BS,\RB\#1\RB\par
3992\BS renewcommand*\LB\BS Messier\RB[1]\LB\BS smaller\LB\BS smaller
3993     M\BS,\RB\#1\RB\par}
3994
3995\medskip The default preamble activates Adobe Symbol Greek letters, and
3996Helvetica Roman letters.  If you define a preamble of your own, you don't have
3997to re-define that, because it's not defined then.  You can assume a naked plain
3998\LaTeX\ font setup.
3999
4000@^hooks in preamble@>
4001@:Label}{\.{\BS Label}@>
4002@:NGC}{\.{\BS NGC}@>
4003@:IC}{\.{\BS IC}@>
4004@:Messier}{\.{\BS Messier}@>
4005@:TextLabel}{\.{\BS TextLabel}@>
4006@:Starname}{\.{\BS Starname}@>
4007@:FlexLabel}{\.{\BS FlexLabel}@>
4008@:TicMark}{\.{\BS TicMark}@>
4009@:DP}{\.{\BS DP}@>
4010@^decimal point@>
4011
4012I define a couple of \LaTeX\ commands as hooks here, all of them take exactly
4013one argument.  ``\.{\BS Label}'' encloses every label.  In addition to that,
4014``\.{\BS NGC}'', ``\.{\BS IC}'', and ``\.{\BS Messier}'' are built around the
4015nebula label of the respective type, ``\.{\BS Starname}'' around all star
4016labels, ``\.{\BS TextLabel}'' around text labels, and ``\.{\BS FlexLabel}''
4017around flex labels.  ``\.{\BS TicMark}'' encloses all coordinate labels
4018generated with the \.{tics} option.  By default, they do nothing.  Well,
4019nothing to speak of.
4020
4021@^preamble (\LaTeX)@>
4022
4023@<|create_preamble()| for writing the \LaTeX\ preamble@>=
4024void create_preamble(ostream& out) {
4025    out << "\\documentclass[" << params.font_size << "pt]{article}\n\n" @/
4026        << "\\nofiles" @/
4027        << "\\usepackage[dvips]{color}\n" @/
4028        << "\\usepackage{pstricks,pst-text}\n" @/
4029        << "\\newcommand*{\\DP}{.}\n" @/
4030        << "\\newcommand*{\\TicMark}[1]{#1}\n" @/
4031        << "\\newcommand*{\\Label}[1]{#1}\n" @/
4032        << "\\newcommand*{\\TextLabel}[1]{#1}\n" @/
4033        << "\\newcommand*{\\FlexLabel}[1]{#1}\n" @/
4034        << "\\newcommand*{\\Starname}[1]{#1}\n" @/
4035        << "\\newcommand*{\\Messier}[1]{M\\,#1}\n" @/
4036        << "\\newcommand*{\\NGC}[1]{NGC\\,#1}\n" @/
4037        << "\\newcommand*{\\IC}[1]{IC\\,#1}\n\n";
4038
4039    if (params.filename_preamble.empty()) @/
4040        out << "\\usepackage{mathptmx}\n" @/
4041            << "\\usepackage{helvet}\n" @/
4042            << "\\AtBeginDocument{\\sffamily}\n";
4043    else out << "\\input " << params.filename_preamble << '\n';
4044}
4045
4046@ This is almost trivial.  I just close the box structure I began at the end of
4047|@<Create \LaTeX\ header@>| and close the document.
4048
4049@<Create \LaTeX\ footer@>=
4050    OUT << "\\hfill}}\\end{document}\n";
4051
4052@ Here I call \LaTeX, dvips, and/or \PS/2\PDF/ in order to create the
4053output the user wanted to have eventually in the input script.
4054
4055@<Create \EPS/ or \PDF/ file if requested@>=
4056        if (!params.filename_output.empty() && (params.create_eps
4057                                                || params.create_pdf)) {
4058            string commandline = string("latex ") + params.filename_output;
4059            if (system(commandline.c_str()) == 0) {
4060                string base_filename(params.filename_output);
4061                if (base_filename.find('.') != string::npos)
4062                    base_filename.erase(base_filename.find('.'));
4063                commandline = string("dvips -o ") + base_filename + ".eps "
4064                    + base_filename;
4065                if (system(commandline.c_str()) == 0) {
4066                    if (params.create_pdf) {
4067                        commandline = string("ps2pdf ") +
4068                            "-dCompatibility=1.3 " +
4069                            base_filename + ".eps " + base_filename + ".pdf";
4070                        if (system(commandline.c_str()) != 0)
4071                            throw string("ps2pdf call failed: ") + commandline;
4072                    }
4073                } else throw string("dvips call failed: ") + commandline;
4074            } else throw string("LaTeX call failed: ") + commandline;
4075        }
4076
4077
4078@** Index.
4079