]> git.tdb.fi Git - r2c2.git/blob - source/designer/svgexporter.cpp
f7248ef6dfffb5988e85f880c399a4574101ff68
[r2c2.git] / source / designer / svgexporter.cpp
1 #include <cmath>
2 #include <msp/strings/format.h>
3 #include <msp/strings/utils.h>
4 #include "libr2c2/catalogue.h"
5 #include "libr2c2/track.h"
6 #include "libr2c2/tracktype.h"
7 #include "svgexporter.h"
8
9 using namespace std;
10 using namespace Msp;
11 using namespace R2C2;
12
13 SvgExporter::SvgExporter(const Layout &l):
14         layout(l)
15 { }
16
17 void SvgExporter::save(const string &fn)
18 {
19         xmlpp::Document *doc = new xmlpp::Document;
20         root = doc->create_root_node("svg", "http://www.w3.org/2000/svg");
21
22         xmlpp::Element *style = root->add_child("style");
23         style->set_attribute("type", "text/css");
24         styles.push_back(".rail { fill: none; stroke: #000000; }");
25         styles.push_back(".border { fill: none; stroke: #808080; }");
26         styles.push_back(".artnr { text-anchor: middle; font-family: sans; }");
27
28         defs = root->add_child("defs");
29
30         Vector minp;
31         Vector maxp;
32         const set<Track *> &tracks = layout.get_all<Track>();
33         for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
34         {
35                 save_track(**i);
36
37                 float gauge = (*i)->get_type().get_appearance().get_gauge();
38                 unsigned n_endpoints = (*i)->get_type().get_endpoints().size();
39                 for(unsigned j=0; j<n_endpoints; ++j)
40                 {
41                         Vector pos = (*i)->get_snap_node(j).position;
42                         if(i==tracks.begin() && j==0)
43                                 minp = maxp = pos;
44                         else
45                         {
46                                 minp.x = min(minp.x, pos.x-gauge*3);
47                                 minp.y = min(minp.y, pos.y-gauge*3);
48                                 maxp.x = max(maxp.x, pos.x+gauge*3);
49                                 maxp.y = max(maxp.y, pos.y+gauge*3);
50                         }
51                 }
52         }
53
54         style->set_child_text(join(styles.begin(), styles.end(), "\n"));
55
56         root->set_attribute("viewBox", format("%.3f %.3f %.3f %.3f",
57                 minp.x*1000, -maxp.y*1000, (maxp.x-minp.x)*1000, (maxp.y-minp.y)*1000));
58
59         doc->write_to_file_formatted(fn);
60 }
61
62 string SvgExporter::create_appearance(const TrackAppearance &appearance)
63 {
64         float gauge = appearance.get_gauge();
65         string key = format("a%.0f", gauge*1000);
66
67         if(!appearances_created.count(&appearance))
68         {
69                 float rail_width = appearance.get_rail_profile().get_width();
70
71                 styles.push_back(format(".%s .rail { stroke-width: %.3f }", key, rail_width*1000));
72                 styles.push_back(format(".%s .border { stroke-width: %.3f }", key, rail_width*500));
73                 styles.push_back(format(".%s .artnr { font-size: %.3f }", key, gauge*750));
74
75                 appearances_created.insert(&appearance);
76         }
77
78         return key;
79 }
80
81 string SvgExporter::create_point_data(const Vector &p)
82 {
83         return format("%.3f %.3f", p.x*1000, -p.y*1000);
84 }
85
86 string SvgExporter::create_line_data(const Vector &s, const Vector &e)
87 {
88         return format("M %s L %s", create_point_data(s), create_point_data(e));
89 }
90
91 string SvgExporter::create_curve_data(const Vector &s, const Vector &st, const Vector &et, const Vector &e)
92 {
93         return format("M %s C %s %s %s", create_point_data(s),
94                 create_point_data(s+st), create_point_data(e-et), create_point_data(e));
95 }
96
97 string SvgExporter::create_track_type(const TrackType &type)
98 {
99         const ArticleNumber &art_nr = type.get_article_number();
100         string key = format("t%s", art_nr.str());
101
102         if(!tracks_created.count(&type))
103         {
104                 const TrackAppearance &appearance = type.get_appearance();
105                 string appearance_key = create_appearance(appearance);
106
107                 xmlpp::Element *group = defs->add_child("g");
108                 group->set_attribute("id", key);
109                 group->set_attribute("class", appearance_key);
110
111                 float gauge = appearance.get_gauge();
112                 float rail_width = appearance.get_rail_profile().get_width();
113
114                 const vector<TrackType::Endpoint> &endpoints = type.get_endpoints();
115                 for(vector<TrackType::Endpoint>::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
116                 {
117                         xmlpp::Element *path = group->add_child("path");
118                         path->set_attribute("class", "border");
119
120                         Vector delta = rotated_vector(Vector(0, gauge, 0), i->dir);
121
122                         path->set_attribute("d", create_line_data(i->pos+delta, i->pos-delta));
123                 }
124
125                 const vector<TrackPart> &parts = type.get_parts();
126                 for(vector<TrackPart>::const_iterator i=parts.begin(); i!=parts.end(); ++i)
127                 {
128                         xmlpp::Element *path = group->add_child("path");
129                         path->set_attribute("class", "rail");
130
131                         string data;
132
133                         OrientedPoint start = i->get_point(0);
134                         OrientedPoint end = i->get_point(i->get_length());
135                         if(i->is_curved())
136                         {
137                                 Vector delta1 = rotated_vector(Vector(0, (gauge+rail_width)*0.5, 0), start.rotation);
138                                 Vector delta2 = rotated_vector(Vector(0, (gauge+rail_width)*0.5, 0), end.rotation);
139                                 // Largely an educated guess, but seems to be accurate enough
140                                 float clen = i->get_length()/2.9;
141                                 Vector ctrl1 = rotated_vector(Vector(clen, 0, 0), start.rotation);
142                                 Vector ctrl2 = rotated_vector(Vector(clen, 0, 0), end.rotation);
143
144                                 data = create_curve_data(start.position+delta1, ctrl1, ctrl2, end.position+delta2);
145                                 data += ' ';
146                                 data += create_curve_data(start.position-delta1, ctrl1, ctrl2, end.position-delta2);
147                         }
148                         else
149                         {
150                                 Vector delta = rotated_vector(Vector(0, (gauge+rail_width)*0.5, 0), start.rotation);
151
152                                 data = create_line_data(start.position+delta, end.position+delta);
153                                 data += ' ';
154                                 data += create_line_data(start.position-delta ,end.position-delta);
155                         }
156
157                         path->set_attribute("d", data);
158                 }
159
160                 OrientedPoint label_pt = parts.front().get_point(parts.front().get_length()/2);
161
162                 label_pt.rotation.wrap_with_base(-Angle::quarter_turn());
163                 if(label_pt.rotation>Angle::quarter_turn())
164                         label_pt.rotation -= Angle::half_turn();
165
166                 label_pt.position += rotated_vector(Vector(0, -gauge*0.25, 0), label_pt.rotation);
167
168                 xmlpp::Element *text = group->add_child("text");
169                 text->set_attribute("class", "artnr");
170                 text->set_attribute("transform", format("translate(%s) rotate(%.3f)",
171                         create_point_data(label_pt.position), -label_pt.rotation.degrees()));
172                 text->set_child_text(type.get_article_number().str());
173
174                 tracks_created.insert(&type);
175         }
176
177         return key;
178 }
179
180 void SvgExporter::save_track(const Track &track)
181 {
182         string type_key = create_track_type(track.get_type());
183         xmlpp::Element *use = root->add_child("use");
184
185         const Vector &pos = track.get_position();
186         const Angle &rot = track.get_rotation();
187         string transform = format("translate(%s) rotate(%.3f)", create_point_data(pos), -rot.degrees());
188         use->set_attribute("transform", transform);
189
190         use->set_attribute("xlink:href", "#"+type_key);
191 }