From 4c8c03d25ec4d58772ceb5b287deab645fdae19f Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Fri, 2 May 2014 23:53:16 +0300 Subject: [PATCH] Overhaul SvgExporter to use SVG defs for tracks This produces much more compact SVG files than outputting the paths for each track separately. --- source/designer/svgexporter.cpp | 200 +++++++++++++++++++------------- source/designer/svgexporter.h | 13 ++- 2 files changed, 132 insertions(+), 81 deletions(-) diff --git a/source/designer/svgexporter.cpp b/source/designer/svgexporter.cpp index 890112f..74d9ccf 100644 --- a/source/designer/svgexporter.cpp +++ b/source/designer/svgexporter.cpp @@ -1,5 +1,6 @@ #include #include +#include #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 &tracks = layout.get_all(); for(set::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; jset_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 &endpoints = type.get_endpoints(); - for(vector::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 &parts = type.get_parts(); - for(vector::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 &endpoints = type.get_endpoints(); + for(vector::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 &parts = type.get_parts(); + for(vector::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); } diff --git a/source/designer/svgexporter.h b/source/designer/svgexporter.h index e498d3d..20af4d3 100644 --- a/source/designer/svgexporter.h +++ b/source/designer/svgexporter.h @@ -9,14 +9,23 @@ class SvgExporter private: const R2C2::Layout &layout; float gauge; - float rail_width; + xmlpp::Element *root; + xmlpp::Element *defs; + std::set tracks_created; + std::set appearances_created; + std::list 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 -- 2.43.0