]> git.tdb.fi Git - r2c2.git/commitdiff
Overhaul SvgExporter to use SVG defs for tracks
authorMikko Rasa <tdb@tdb.fi>
Fri, 2 May 2014 20:53:16 +0000 (23:53 +0300)
committerMikko Rasa <tdb@tdb.fi>
Fri, 2 May 2014 20:53:16 +0000 (23:53 +0300)
This produces much more compact SVG files than outputting the paths for
each track separately.

source/designer/svgexporter.cpp
source/designer/svgexporter.h

index 890112f84c5d57431ddd81f36e357b1feb46288a..74d9ccf0278be2779095a4f284076bbe38ddc7cb 100644 (file)
@@ -1,5 +1,6 @@
 #include <cmath>
 #include <msp/strings/format.h>
+#include <msp/strings/utils.h>
 #include "libr2c2/catalogue.h"
 #include "libr2c2/track.h"
 #include "libr2c2/tracktype.h"
@@ -11,34 +12,30 @@ using namespace R2C2;
 
 SvgExporter::SvgExporter(const Layout &l):
        layout(l),
-       gauge(0),
-       rail_width(0)
+       gauge(0)
 { }
 
 void SvgExporter::save(const string &fn)
 {
-       gauge = layout.get_catalogue().get_gauge()*1000;
-
-       // XXX This should be retrieved from track appearance
-       rail_width = 2;
+       gauge = layout.get_catalogue().get_gauge();
 
        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(format(".artnr { text-anchor: middle; font-family: sans; font-size: %.3f; }", gauge*750));
+
+       defs = root->add_child("defs");
 
        Vector minp;
        Vector maxp;
        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);
 
                unsigned n_endpoints = (*i)->get_type().get_endpoints().size();
                for(unsigned j=0; j<n_endpoints; ++j)
@@ -56,93 +53,138 @@ void SvgExporter::save(const string &fn)
                }
        }
 
+       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));
 
        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();
-       const Angle &rot = track.get_rotation();
-       string transform = format("translate(%.3f %.3f) rotate(%.3f)", pos.x*1000, -pos.y*1000, -rot.degrees());
-       group.set_attribute("transform", transform);
-
-       const TrackType &type = track.get_type();
+       string key = format("a%p", &appearance);
 
-       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();
 
-               Vector delta = rotated_vector(Vector(0, gauge, 0), i->dir);
+               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));
 
-               string data = format("M %.3f %.3f L %.3f %.3f",
-                       i->pos.x*1000+delta.x, -i->pos.y*1000-delta.y,
-                       i->pos.x*1000-delta.x, -i->pos.y*1000+delta.y);
-               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))
        {
-               OrientedPoint start = i->get_point(0);
-               OrientedPoint 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 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");
-
-                       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()*1000/2.9;
-                       Vector ctrl1 = rotated_vector(Vector(clen, 0, 0), start.rotation);
-                       Vector ctrl2 = rotated_vector(Vector(clen, 0, 0), end.rotation);
-
-                       string data = format("M %.3f %.3f C %.3f %.3f %.3f %.3f %.3f %.3f")
-                               (start.position.x*1000+delta1.x)(-start.position.y*1000-delta1.y)
-                               (start.position.x*1000+delta1.x+ctrl1.x)(-start.position.y*1000-delta1.y-ctrl1.y)
-                               (end.position.x*1000+delta2.x-ctrl2.x)(-end.position.y*1000-delta2.y+ctrl2.y)
-                               (end.position.x*1000+delta2.x)(-end.position.y*1000-delta2.y).str();
-                       data += format(" M %.3f %.3f C %.3f %.3f %.3f %.3f %.3f %.3f")
-                               (start.position.x*1000-delta1.x)(-start.position.y*1000+delta1.y)
-                               (start.position.x*1000-delta1.x+ctrl1.x)(-start.position.y*1000+delta1.y-ctrl1.y)
-                               (end.position.x*1000-delta2.x-ctrl2.x)(-end.position.y*1000+delta2.y+ctrl2.y)
-                               (end.position.x*1000-delta2.x)(-end.position.y*1000+delta2.y).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");
-
-                       Vector delta = rotated_vector(Vector(0, (gauge+rail_width)*0.5, 0), start.rotation);
-
-                       string data = format("M %.3f %.3f L %.3f %.3f",
-                               start.position.x*1000+delta.x, -start.position.y*1000-delta.y,
-                               end.position.x*1000+delta.x, -end.position.y*1000-delta.y);
-                       data += format(" M %.3f %.3f L %.3f %.3f",
-                               start.position.x*1000-delta.x, -start.position.y*1000+delta.y,
-                               end.position.x*1000-delta.x, -end.position.y*1000+delta.y);
-                       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);
        }
 
-       OrientedPoint label_pt = parts.front().get_point(parts.front().get_length()/2);
+       return key;
+}
 
-       label_pt.rotation.wrap_with_base(-Angle::quarter_turn());
-       if(label_pt.rotation>Angle::quarter_turn())
-               label_pt.rotation -= Angle::half_turn();
+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.position *= 1000;
-       label_pt.position += rotated_vector(Vector(0, -gauge*0.25, 0), label_pt.rotation);
+       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.position.x, -label_pt.position.y, -label_pt.rotation.degrees()));
-       elem->set_child_text(track.get_type().get_article_number().str());
+       use->set_attribute("xlink:href", "#"+type_key);
 }
index e498d3ddd38048b845e436eeaf74225bfea70faf..20af4d3eda370c0fb7f287efa7951dc6929ab583 100644 (file)
@@ -9,14 +9,23 @@ class SvgExporter
 private:
        const R2C2::Layout &layout;
        float gauge;
-       float rail_width;
+       xmlpp::Element *root;
+       xmlpp::Element *defs;
+       std::set<const R2C2::TrackType *> tracks_created;
+       std::set<const R2C2::TrackAppearance *> appearances_created;
+       std::list<std::string> styles;
 
 public:
        SvgExporter(const R2C2::Layout &);
 
        void save(const std::string &);
 private:
-       void save_track(const R2C2::Track &, xmlpp::Element &);
+       std::string create_appearance(const R2C2::TrackAppearance &);
+       std::string create_point_data(const R2C2::Vector &);
+       std::string create_line_data(const R2C2::Vector &, const R2C2::Vector &);
+       std::string create_curve_data(const R2C2::Vector &, const R2C2::Vector &, const R2C2::Vector &, const R2C2::Vector &);
+       std::string create_track_type(const R2C2::TrackType &);
+       void save_track(const R2C2::Track &);
 };
 
 #endif