-/* $Id$
-
-This file is part of R²C²
-Copyright © 2010 Mikkosoft Productions, Mikko Rasa
-Distributed under the GPL
-*/
-
#include <cmath>
-#include <msp/strings/formatter.h>
+#include <msp/strings/format.h>
+#include <msp/strings/utils.h>
#include "libr2c2/catalogue.h"
#include "libr2c2/track.h"
#include "libr2c2/tracktype.h"
using namespace R2C2;
SvgExporter::SvgExporter(const Layout &l):
- layout(l),
- gauge(0),
- rail_width(0)
+ layout(l)
{ }
void SvgExporter::save(const string &fn)
{
- gauge = layout.get_catalogue().get_gauge()*1000;
-
- const Profile &rail_profile = layout.get_catalogue().get_rail_profile();
- const Vector &rail_min = rail_profile.get_min_coords();
- const Vector &rail_max = rail_profile.get_max_coords();
- rail_width = (rail_max.x-rail_min.x)*1000;
-
xmlpp::Document *doc = new xmlpp::Document;
- xmlpp::Element *root = doc->create_root_node("svg", "http://www.w3.org/2000/svg");
+ root = doc->create_root_node("svg", "http://www.w3.org/2000/svg");
xmlpp::Element *style = root->add_child("style");
style->set_attribute("type", "text/css");
- style->set_child_text(format("\n.rail { fill: none; stroke: #000000; stroke-width: %.3f; }\n"
- ".endpoint { fill: none; stroke: #808080; stroke-width: %.3f; }\n"
- ".artnr { text-anchor: middle; font-size: %.3f; }\n",
- rail_width, rail_width, gauge*0.75));
+ styles.push_back(".rail { fill: none; stroke: #000000; }");
+ styles.push_back(".border { fill: none; stroke: #808080; }");
+ styles.push_back(".artnr { text-anchor: middle; font-family: sans; }");
+
+ defs = root->add_child("defs");
Vector minp;
Vector maxp;
- const set<Track *> &tracks = layout.get_tracks();
+ const set<Track *> &tracks = layout.get_all<Track>();
for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
{
- xmlpp::Element *elem = root->add_child("g");
- save_track(**i, *elem);
+ save_track(**i);
+ float gauge = (*i)->get_type().get_gauge();
unsigned n_endpoints = (*i)->get_type().get_endpoints().size();
for(unsigned j=0; j<n_endpoints; ++j)
{
- Vector pos = (*i)->get_endpoint_position(j);
+ Vector pos = (*i)->get_snap_node(j).position;
if(i==tracks.begin() && j==0)
minp = maxp = pos;
else
{
- minp.x = min(minp.x, pos.x);
- minp.y = min(minp.y, pos.y);
- maxp.x = max(maxp.x, pos.x);
- maxp.y = max(maxp.y, pos.y);
+ minp.x = min(minp.x, pos.x-gauge*3);
+ minp.y = min(minp.y, pos.y-gauge*3);
+ maxp.x = max(maxp.x, pos.x+gauge*3);
+ maxp.y = max(maxp.y, pos.y+gauge*3);
}
}
}
+ style->set_child_text(join(styles.begin(), styles.end(), "\n"));
+
root->set_attribute("viewBox", format("%.3f %.3f %.3f %.3f",
- minp.x*1000-gauge*3, -maxp.y*1000-gauge*3, (maxp.x-minp.x)*1000+gauge*6, (maxp.y-minp.y)*1000+gauge*6));
+ minp.x*1000, -maxp.y*1000, (maxp.x-minp.x)*1000, (maxp.y-minp.y)*1000));
doc->write_to_file_formatted(fn);
}
-void SvgExporter::save_track(const Track &track, xmlpp::Element &group)
+string SvgExporter::create_appearance(const TrackAppearance &appearance)
{
- const Vector &pos = track.get_position();
- float rot = track.get_rotation();
- string transform = format("translate(%.3f %.3f) rotate(%.3f)", pos.x*1000, -pos.y*1000, -rot*180/M_PI);
- group.set_attribute("transform", transform);
-
- const TrackType &type = track.get_type();
+ float gauge = appearance.get_gauge();
+ string key = format("a%.0f", gauge*1000);
- const vector<TrackType::Endpoint> &endpoints = type.get_endpoints();
- for(vector<TrackType::Endpoint>::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
+ if(!appearances_created.count(&appearance))
{
- xmlpp::Element *elem = group.add_child("path");
- elem->set_attribute("class", "endpoint");
+ float rail_width = appearance.get_rail_profile().get_width();
- float dx = -sin(i->dir)*gauge;
- float dy = -cos(i->dir)*gauge;
+ styles.push_back(format(".%s .rail { stroke-width: %.3f }", key, rail_width*1000));
+ styles.push_back(format(".%s .border { stroke-width: %.3f }", key, rail_width*500));
+ styles.push_back(format(".%s .artnr { font-size: %.3f }", key, gauge*750));
- string data = format("M %.3f %.3f L %.3f %.3f",
- i->pos.x*1000+dx, -i->pos.y*1000+dy,
- i->pos.x*1000-dx, -i->pos.y*1000-dy);
- elem->set_attribute("d", data);
+ appearances_created.insert(&appearance);
}
- const vector<TrackPart> &parts = type.get_parts();
- for(vector<TrackPart>::const_iterator i=parts.begin(); i!=parts.end(); ++i)
+ return key;
+}
+
+string SvgExporter::create_point_data(const Vector &p)
+{
+ return format("%.3f %.3f", p.x*1000, -p.y*1000);
+}
+
+string SvgExporter::create_line_data(const Vector &s, const Vector &e)
+{
+ return format("M %s L %s", create_point_data(s), create_point_data(e));
+}
+
+string SvgExporter::create_curve_data(const Vector &s, const Vector &st, const Vector &et, const Vector &e)
+{
+ return format("M %s C %s %s %s", create_point_data(s),
+ create_point_data(s+st), create_point_data(e-et), create_point_data(e));
+}
+
+string SvgExporter::create_track_type(const TrackType &type)
+{
+ const ArticleNumber &art_nr = type.get_article_number();
+ string key = format("t%s", art_nr.str());
+
+ if(!tracks_created.count(&type))
{
- TrackPoint start = i->get_point(0);
- TrackPoint end = i->get_point(i->get_length());
- if(i->is_curved())
+ const TrackAppearance &appearance = type.get_appearance();
+ string appearance_key = create_appearance(appearance);
+
+ xmlpp::Element *group = defs->add_child("g");
+ group->set_attribute("id", key);
+ group->set_attribute("class", appearance_key);
+
+ float gauge = appearance.get_gauge();
+ float rail_width = appearance.get_rail_profile().get_width();
+
+ const vector<TrackType::Endpoint> &endpoints = type.get_endpoints();
+ for(vector<TrackType::Endpoint>::const_iterator i=endpoints.begin(); i!=endpoints.end(); ++i)
{
- xmlpp::Element *elem = group.add_child("path");
- elem->set_attribute("class", "rail");
-
- float cs = cos(start.dir);
- float ss = sin(start.dir);
- float ce = cos(end.dir);
- float se = sin(end.dir);
- float dx1 = -ss*(gauge+rail_width)*0.5;
- float dy1 = -cs*(gauge+rail_width)*0.5;
- float dx2 = -se*(gauge+rail_width)*0.5;
- float dy2 = -ce*(gauge+rail_width)*0.5;
- // Largely an educated guess, but seems to be accurate enough
- float clen = i->get_length()*1000/2.9;
-
- string data = format("M %.3f %.3f C %.3f %.3f %.3f %.3f %.3f %.3f")
- (start.pos.x*1000+dx1)(-start.pos.y*1000+dy1)
- (start.pos.x*1000+dx1+cs*clen)(-start.pos.y*1000+dy1-ss*clen)
- (end.pos.x*1000+dx2-ce*clen)(-end.pos.y*1000+dy2+se*clen)
- (end.pos.x*1000+dx2)(-end.pos.y*1000+dy2).str();
- data += format(" M %.3f %.3f C %.3f %.3f %.3f %.3f %.3f %.3f")
- (start.pos.x*1000-dx1)(-start.pos.y*1000-dy1)
- (start.pos.x*1000-dx1+cs*clen)(-start.pos.y*1000-dy1-ss*clen)
- (end.pos.x*1000-dx2-ce*clen)(-end.pos.y*1000-dy2+se*clen)
- (end.pos.x*1000-dx2)(-end.pos.y*1000-dy2).str();
- elem->set_attribute("d", data);
+ xmlpp::Element *path = group->add_child("path");
+ path->set_attribute("class", "border");
+
+ Vector delta = rotated_vector(Vector(0, gauge, 0), i->dir);
+
+ path->set_attribute("d", create_line_data(i->pos+delta, i->pos-delta));
}
- else
+
+ const vector<TrackPart> &parts = type.get_parts();
+ for(vector<TrackPart>::const_iterator i=parts.begin(); i!=parts.end(); ++i)
{
- xmlpp::Element *elem = group.add_child("path");
- elem->set_attribute("class", "rail");
-
- float dx = -sin(start.dir)*(gauge+rail_width)*0.5;
- float dy = -cos(start.dir)*(gauge+rail_width)*0.5;
-
- string data = format("M %.3f %.3f L %.3f %.3f",
- start.pos.x*1000+dx, -start.pos.y*1000+dy,
- end.pos.x*1000+dx, -end.pos.y*1000+dy);
- data += format(" M %.3f %.3f L %.3f %.3f",
- start.pos.x*1000-dx, -start.pos.y*1000-dy,
- end.pos.x*1000-dx, -end.pos.y*1000-dy);
- elem->set_attribute("d", data);
+ xmlpp::Element *path = group->add_child("path");
+ path->set_attribute("class", "rail");
+
+ string data;
+
+ OrientedPoint start = i->get_point(0);
+ OrientedPoint end = i->get_point(i->get_length());
+ if(i->is_curved())
+ {
+ Vector delta1 = rotated_vector(Vector(0, (gauge+rail_width)*0.5, 0), start.rotation);
+ Vector delta2 = rotated_vector(Vector(0, (gauge+rail_width)*0.5, 0), end.rotation);
+ // Largely an educated guess, but seems to be accurate enough
+ float clen = i->get_length()/2.9;
+ Vector ctrl1 = rotated_vector(Vector(clen, 0, 0), start.rotation);
+ Vector ctrl2 = rotated_vector(Vector(clen, 0, 0), end.rotation);
+
+ data = create_curve_data(start.position+delta1, ctrl1, ctrl2, end.position+delta2);
+ data += ' ';
+ data += create_curve_data(start.position-delta1, ctrl1, ctrl2, end.position-delta2);
+ }
+ else
+ {
+ Vector delta = rotated_vector(Vector(0, (gauge+rail_width)*0.5, 0), start.rotation);
+
+ data = create_line_data(start.position+delta, end.position+delta);
+ data += ' ';
+ data += create_line_data(start.position-delta ,end.position-delta);
+ }
+
+ path->set_attribute("d", data);
}
+
+ OrientedPoint label_pt = parts.front().get_point(parts.front().get_length()/2);
+
+ label_pt.rotation.wrap_with_base(-Angle::quarter_turn());
+ if(label_pt.rotation>Angle::quarter_turn())
+ label_pt.rotation -= Angle::half_turn();
+
+ label_pt.position += rotated_vector(Vector(0, -gauge*0.25, 0), label_pt.rotation);
+
+ xmlpp::Element *text = group->add_child("text");
+ text->set_attribute("class", "artnr");
+ text->set_attribute("transform", format("translate(%s) rotate(%.3f)",
+ create_point_data(label_pt.position), -label_pt.rotation.degrees()));
+ text->set_child_text(type.get_article_number().str());
+
+ tracks_created.insert(&type);
}
- TrackPoint label_pt = parts.front().get_point(parts.front().get_length()/2);
+ return key;
+}
- while(rot+label_pt.dir>M_PI/2)
- label_pt.dir -= M_PI;
- while(rot+label_pt.dir<-M_PI/2)
- label_pt.dir += M_PI;
+void SvgExporter::save_track(const Track &track)
+{
+ string type_key = create_track_type(track.get_type());
+ xmlpp::Element *use = root->add_child("use");
- label_pt.pos.x = label_pt.pos.x*1000+sin(label_pt.dir)*gauge*0.25;
- label_pt.pos.y = label_pt.pos.y*1000-cos(label_pt.dir)*gauge*0.25;
+ const Vector &pos = track.get_position();
+ const Angle &rot = track.get_rotation();
+ string transform = format("translate(%s) rotate(%.3f)", create_point_data(pos), -rot.degrees());
+ use->set_attribute("transform", transform);
- xmlpp::Element *elem = group.add_child("text");
- elem->set_attribute("class", "artnr");
- elem->set_attribute("transform", format("translate(%.3f %.3f) rotate(%.3f)",
- label_pt.pos.x, -label_pt.pos.y, -label_pt.dir*180/M_PI));
- elem->set_child_text(track.get_type().get_article_number().str());
+ use->set_attribute("xlink:href", "#"+type_key);
}