// SPDX-License-Identifier: GPL-2.0-or-later /* * Author(s): * Jabiertxo Arraiza Cenoz * Some code and ideas migrated from dimensioning.py by * Johannes B. Rutzmoser, johannes.rutzmoser (at) googlemail (dot) com * https://github.com/Rutzmoser/inkscape_dimensioning * Copyright (C) 2014 Author(s) * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "live_effects/lpeobject.h" #include "live_effects/lpeobject-reference.h" #include "live_effects/lpe-measure-segments.h" #include "2geom/affine.h" #include "2geom/angle.h" #include "2geom/point.h" #include "2geom/ray.h" #include "display/curve.h" #include "helper/geom.h" #include "text-editing.h" #include "object/sp-defs.h" #include "object/sp-text.h" #include "object/sp-flowtext.h" #include "object/sp-item-group.h" #include "object/sp-item.h" #include "object/sp-path.h" #include "object/sp-root.h" #include "object/sp-shape.h" #include "svg/stringstream.h" #include "svg/svg.h" #include "svg/svg-color.h" #include "svg/svg-length.h" #include "util/units.h" #include "xml/node.h" #include "xml/sp-css-attr.h" #include "libnrtype/Layout-TNG.h" #include "document.h" #include "document-undo.h" #include "inkscape.h" #include "preferences.h" #include "path-chemistry.h" #include #include #include #include // TODO due to internal breakage in glibmm headers, this must be last: #include using namespace Geom; namespace Inkscape { namespace LivePathEffect { static const Util::EnumData OrientationMethodData[] = { { OM_HORIZONTAL , N_("Horizontal"), "horizontal" }, { OM_VERTICAL , N_("Vertical") , "vertical" }, { OM_PARALLEL , N_("Parallel") , "parallel" } }; static const Util::EnumDataConverter OMConverter(OrientationMethodData, OM_END); LPEMeasureSegments::LPEMeasureSegments(LivePathEffectObject *lpeobject) : Effect(lpeobject), unit(_("Unit"), _("Unit"), "unit", &wr, this, "mm"), orientation(_("Orientation"), _("Orientation method"), "orientation", OMConverter, &wr, this, OM_PARALLEL, false), coloropacity(_("Color and opacity"), _("Set color and opacity of the measurements"), "coloropacity", &wr, this, 0x000000ff), fontbutton(_("Font"), _("Font Selector"), "fontbutton", &wr, this), precision(_("Precision"), _("Precision"), "precision", &wr, this, 2), fix_overlaps(_("Fix overlaps °"), _("Min angle where overlaps are fixed, 180° not fixed"), "fix_overlaps", &wr, this, 0), position(_("Position"), _("Position"), "position", &wr, this, 5), text_top_bottom(_("Text top/bottom"), _("Text top/bottom"), "text_top_bottom", &wr, this, 0), helpline_distance(_("Helpline distance"), _("Helpline distance"), "helpline_distance", &wr, this, 0.0), helpline_overlap(_("Helpline overlap"), _("Helpline overlap"), "helpline_overlap", &wr, this, 2.0), line_width(_("Line width"), _("Line width. DIM line group standard are 0.25 or 0.35"), "line_width", &wr, this, 0.25), scale(_("Scale"), _("Scaling factor"), "scale", &wr, this, 1.0), format(_("Format"), _("Format the number ex:{measure} {unit}, return to save"), "format", &wr, this,"{measure}{unit}"), blacklist(_("Blacklist"), _("Optional segment index that exclude measurement, comma limited, you can add more LPE like this to fill the holes"), "blacklist", &wr, this,""), whitelist(_("Inverse blacklist"), _("Blacklist as whitelist"), "whitelist", &wr, this, false), showindex(_("Show measure number"), _("Show measurement number in text label for blacklisting"), "showindex", &wr, this, false), arrows_outside(_("Arrows outside"), _("Arrows outside"), "arrows_outside", &wr, this, false), flip_side(_("Flip side"), _("Flip side"), "flip_side", &wr, this, false), scale_sensitive(_("Scale sensitive"), _("Constrained scale sensitive to transformed containers"), "scale_sensitive", &wr, this, true), local_locale(_("Local Number Format"), _("Local number format"), "local_locale", &wr, this, true), rotate_anotation(_("Rotate Annotation"), _("Rotate Annotation"), "rotate_anotation", &wr, this, true), hide_back(_("Hide if label over"), _("Hide DIN line if label over"), "hide_back", &wr, this, true), hide_arrows(_("Hide arrows"), _("Hide arrows"), "hide_arrows", &wr, this, false), smallx100(_("Multiply lower 1"), _("Multiply by 100 less than 1"), "smallx100", &wr, this, false), linked_items(_("Linked items:"), _("Items that generate a measured projection with its nodes"), "linked_items", &wr, this), distance_projection(_("Distance"), _("Distance away from nearest point"), "distance_projection", &wr, this, 20.0), angle_projection(_("Angle of projection"), _("Angle of projection"), "angle_projection", &wr, this, 0.0), active_projection(_("Activate projection"), _("Active projection mode"), "active_projection", &wr, this, false), avoid_overlapping(_("Avoid overlap measurements"), _("Turn doesn't fit measurements"), "avoid_overlapping", &wr, this, true), onbbox(_("Measure bounding box"), _("Measure geometric bounding box"), "onbbox", &wr, this, false), bboxonly(_("Only bounding box"), _("Measure only bbox and hide nodes"), "bboxonly", &wr, this, false), centers(_("Project center"), _("Use centers as measurement"), "centers", &wr, this, false), maxmin(_("Only max and min"), _("Compute only max/min projection values"), "maxmin", &wr, this, false), helpdata(_("Help"), _("Measure segments help"), "helpdata", &wr, this, "", "") { //set to true the parameters you want to be changed his default values registerParameter(&unit); registerParameter(&orientation); registerParameter(&coloropacity); registerParameter(&fontbutton); registerParameter(&precision); registerParameter(&fix_overlaps); registerParameter(&position); registerParameter(&text_top_bottom); registerParameter(&helpline_distance); registerParameter(&helpline_overlap); registerParameter(&line_width); registerParameter(&scale); registerParameter(&format); registerParameter(&blacklist); registerParameter(&active_projection); registerParameter(&whitelist); registerParameter(&showindex); registerParameter(&arrows_outside); registerParameter(&flip_side); registerParameter(&scale_sensitive); registerParameter(&local_locale); registerParameter(&rotate_anotation); registerParameter(&hide_back); registerParameter(&hide_arrows); registerParameter(&smallx100); registerParameter(&linked_items); registerParameter(&distance_projection); registerParameter(&angle_projection); registerParameter(&avoid_overlapping); registerParameter(&onbbox); registerParameter(&bboxonly); registerParameter(¢ers); registerParameter(&maxmin); registerParameter(&helpdata); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring format_value = prefs->getString("/live_effects/measure-line/format"); if(format_value.empty()){ format_value = "{measure}{unit}"; } format.param_update_default(format_value.c_str()); format.param_hide_canvas_text(); blacklist.param_hide_canvas_text(); precision.param_set_range(0, 100); precision.param_set_increments(1, 1); precision.param_set_digits(0); precision.param_make_integer(true); fix_overlaps.param_set_range(0, 180); fix_overlaps.param_set_increments(1, 1); fix_overlaps.param_set_digits(0); fix_overlaps.param_make_integer(true); position.param_set_range(-999999.0, 999999.0); position.param_set_increments(1, 1); position.param_set_digits(2); scale.param_set_range(-999999.0, 999999.0); scale.param_set_increments(1, 1); scale.param_set_digits(4); text_top_bottom.param_set_range(-999999.0, 999999.0); text_top_bottom.param_set_increments(1, 1); text_top_bottom.param_set_digits(2); line_width.param_set_range(0, 999999.0); line_width.param_set_increments(0.1, 0.1); line_width.param_set_digits(2); helpline_distance.param_set_range(-999999.0, 999999.0); helpline_distance.param_set_increments(1, 1); helpline_distance.param_set_digits(2); helpline_overlap.param_set_range(-999999.0, 999999.0); helpline_overlap.param_set_increments(1, 1); helpline_overlap.param_set_digits(2); distance_projection.param_set_range(-999999.0, 999999.0); distance_projection.param_set_increments(1, 1); distance_projection.param_set_digits(5); angle_projection.param_set_range(0.0, 360.0); angle_projection.param_set_increments(90.0, 90.0); angle_projection.param_set_digits(2); locale_base = strdup(setlocale(LC_NUMERIC, nullptr)); previous_size = 0; pagenumber = 0; helpdata.param_update_default(_("General\n" "Measure display and positioning\n\n" "Projection\n" "Show a line with measurements based on the selected items\n\n" "Options\n" "That usually don't change much\n\n" "Tips\n" "Style Dialog Styling using XML editor to find appropriate classes or ID's\n" "Blacklists This allow hiding some segments or projection steps to measure\n" "Multiple Measures In the same object, in conjunction of blacklists, " "allow measures with different orientations or a extra projection \n" "Set Defaults Remember all LPE has it in the bottom")); } LPEMeasureSegments::~LPEMeasureSegments() { doOnRemove(nullptr); } Gtk::Widget * LPEMeasureSegments::newWidget() { // use manage here, because after deletion of Effect object, others might still be pointing to this widget. Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() ); vbox->set_border_width(0); vbox->set_homogeneous(false); vbox->set_spacing(0); Gtk::VBox *vbox0 = Gtk::manage(new Gtk::VBox()); vbox0->set_border_width(5); vbox0->set_homogeneous(false); vbox0->set_spacing(2); Gtk::VBox *vbox1 = Gtk::manage(new Gtk::VBox()); vbox1->set_border_width(5); vbox1->set_homogeneous(false); vbox1->set_spacing(2); Gtk::VBox *vbox2 = Gtk::manage(new Gtk::VBox()); vbox2->set_border_width(5); vbox2->set_homogeneous(false); vbox2->set_spacing(2); //Help page Gtk::VBox *vbox3 = Gtk::manage(new Gtk::VBox()); vbox3->set_border_width(5); vbox3->set_homogeneous(false); vbox3->set_spacing(2); std::vector::iterator it = param_vector.begin(); while (it != param_vector.end()) { if ((*it)->widget_is_visible) { Parameter * param = *it; Gtk::Widget * widg = param->param_newWidget(); Glib::ustring * tip = param->param_getTooltip(); if (widg) { if ( param->param_key == "linked_items") { vbox1->pack_start(*widg, true, true, 2); } else if (param->param_key == "active_projection" || param->param_key == "distance_projection" || param->param_key == "angle_projection" || param->param_key == "maxmin" || param->param_key == "centers" || param->param_key == "bboxonly" || param->param_key == "onbbox" ) { vbox1->pack_start(*widg, false, true, 2); } else if (param->param_key == "precision" || param->param_key == "coloropacity" || param->param_key == "font" || param->param_key == "format" || param->param_key == "blacklist" || param->param_key == "whitelist" || param->param_key == "showindex" || param->param_key == "local_locale" || param->param_key == "smallx100" || param->param_key == "hide_arrows" ) { vbox2->pack_start(*widg, false, true, 2); } else if (param->param_key == "helpdata") { vbox3->pack_start(*widg, false, true, 2); } else { vbox0->pack_start(*widg, false, true, 2); } if (tip) { widg->set_tooltip_text(*tip); } else { widg->set_tooltip_text(""); widg->set_has_tooltip(false); } } } ++it; } Gtk::Notebook * notebook = Gtk::manage(new Gtk::Notebook()); notebook->append_page (*vbox0, Glib::ustring(_("General"))); notebook->append_page (*vbox1, Glib::ustring(_("Projection"))); notebook->append_page (*vbox2, Glib::ustring(_("Options"))); notebook->append_page (*vbox3, Glib::ustring(_("Help"))); vbox0->show_all(); vbox1->show_all(); vbox2->show_all(); vbox3->show_all(); vbox->pack_start(*notebook, true, true, 2); notebook->set_current_page(pagenumber); notebook->signal_switch_page().connect(sigc::mem_fun(*this, &LPEMeasureSegments::on_my_switch_page)); if(Gtk::Widget* widg = defaultParamSet()) { //Wrap to make it more omogenious Gtk::VBox *vbox4 = Gtk::manage(new Gtk::VBox()); vbox4->set_border_width(5); vbox4->set_homogeneous(false); vbox4->set_spacing(2); vbox4->pack_start(*widg, true, true, 2); vbox->pack_start(*vbox4, true, true, 2); } return dynamic_cast(vbox); } void LPEMeasureSegments::on_my_switch_page(Gtk::Widget* page, guint page_number) { if(!page->get_parent()->in_destruction()) { pagenumber = page_number; } } void LPEMeasureSegments::createArrowMarker(Glib::ustring mode) { SPDocument * document = SP_ACTIVE_DOCUMENT; if (!document || !sp_lpe_item|| !sp_lpe_item->getId()) { return; } Glib::ustring lpobjid = this->lpeobj->getId(); Glib::ustring itemid = sp_lpe_item->getId(); Glib::ustring style; style = Glib::ustring("fill:context-stroke;"); Inkscape::SVGOStringStream os; os << SP_RGBA32_A_F(coloropacity.get_value()); style = style + Glib::ustring(";fill-opacity:") + Glib::ustring(os.str()); style = style + Glib::ustring(";stroke:none"); Inkscape::XML::Document *xml_doc = document->getReprDoc(); SPObject *elemref = nullptr; Inkscape::XML::Node *arrow = nullptr; if ((elemref = document->getObjectById(mode.c_str()))) { Inkscape::XML::Node *arrow= elemref->getRepr(); if (arrow) { arrow->setAttribute("sodipodi:insensitive", "true"); arrow->setAttribute("transform", nullptr); Inkscape::XML::Node *arrow_data = arrow->firstChild(); if (arrow_data) { arrow_data->setAttribute("transform", nullptr); arrow_data->setAttribute("style", style.c_str()); } } } else { arrow = xml_doc->createElement("svg:marker"); arrow->setAttribute("id", mode.c_str()); Glib::ustring classarrow = itemid; classarrow += " "; classarrow += lpobjid; classarrow += " measure-arrows-marker"; arrow->setAttribute("class", classarrow.c_str()); arrow->setAttribute("inkscape:stockid", mode.c_str()); arrow->setAttribute("orient", "auto"); arrow->setAttribute("refX", "0.0"); arrow->setAttribute("refY", "0.0"); arrow->setAttribute("sodipodi:insensitive", "true"); /* Create */ Inkscape::XML::Node *arrow_path = xml_doc->createElement("svg:path"); if (std::strcmp(mode.c_str(), "ArrowDIN-start") == 0) { arrow_path->setAttribute("d", "M -8,0 8,-2.11 8,2.11 z"); } else if (std::strcmp(mode.c_str(), "ArrowDIN-end") == 0) { arrow_path->setAttribute("d", "M 8,0 -8,2.11 -8,-2.11 z"); } else if (std::strcmp(mode.c_str(), "ArrowDINout-start") == 0) { arrow_path->setAttribute("d", "M 0,0 -16,2.11 -16,0.5 -26,0.5 -26,-0.5 -16,-0.5 -16,-2.11 z"); } else { arrow_path->setAttribute("d", "M 0,0 16,-2.11 16,-0.5 26,-0.5 26,0.5 16,0.5 16,2.11 z"); } Glib::ustring classarrowpath = itemid; classarrowpath += " "; classarrowpath += lpobjid; classarrowpath += " measure-arrows"; arrow_path->setAttribute("class", classarrowpath.c_str()); Glib::ustring arrowpath = mode + Glib::ustring("_path"); arrow_path->setAttribute("id", arrowpath.c_str()); arrow_path->setAttribute("style", style.c_str()); arrow->addChild(arrow_path, nullptr); Inkscape::GC::release(arrow_path); elemref = SP_OBJECT(document->getDefs()->appendChildRepr(arrow)); Inkscape::GC::release(arrow); } items.push_back(mode); } void LPEMeasureSegments::createTextLabel(Geom::Point pos, size_t counter, double length, Geom::Coord angle, bool remove, bool valid) { SPDocument * document = SP_ACTIVE_DOCUMENT; if (!document || !sp_lpe_item || !sp_lpe_item->getId()) { return; } Inkscape::XML::Document *xml_doc = document->getReprDoc(); Inkscape::XML::Node *rtext = nullptr; Glib::ustring lpobjid = this->lpeobj->getId(); Glib::ustring itemid = sp_lpe_item->getId(); Glib::ustring id = Glib::ustring("text-on-"); id += Glib::ustring::format(counter); id += "-"; id += lpobjid; SPObject *elemref = nullptr; Inkscape::XML::Node *rtspan = nullptr; elemref = document->getObjectById(id.c_str()); if (elemref) { rtext = elemref->getRepr(); sp_repr_set_svg_double(rtext, "x", pos[Geom::X]); sp_repr_set_svg_double(rtext, "y", pos[Geom::Y]); rtext->setAttribute("sodipodi:insensitive", "true"); rtext->setAttribute("transform", nullptr); } else { rtext = xml_doc->createElement("svg:text"); rtext->setAttribute("xml:space", "preserve"); rtext->setAttribute("id", id.c_str()); Glib::ustring classlabel = itemid; classlabel += " "; classlabel += lpobjid; classlabel += " measure-labels"; rtext->setAttribute("class", classlabel.c_str()); rtext->setAttribute("sodipodi:insensitive", "true"); sp_repr_set_svg_double(rtext, "x", pos[Geom::X]); sp_repr_set_svg_double(rtext, "y", pos[Geom::Y]); rtspan = xml_doc->createElement("svg:tspan"); rtspan->setAttribute("sodipodi:role", "line"); } SPCSSAttr *css = sp_repr_css_attr_new(); Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance(); gchar * fontbutton_str = fontbutton.param_getSVGValue(); fontlister->fill_css(css, Glib::ustring(fontbutton_str)); g_free(fontbutton_str); std::stringstream font_size; setlocale (LC_NUMERIC, "C"); font_size << fontsize << "pt"; setlocale (LC_NUMERIC, locale_base); gchar c[32]; sprintf(c, "#%06x", rgb32 >> 8); sp_repr_css_set_property (css, "fill",c); Inkscape::SVGOStringStream os; os << SP_RGBA32_A_F(coloropacity.get_value()); sp_repr_css_set_property (css, "fill-opacity",os.str().c_str()); if (!rtspan) { rtspan = rtext->firstChild(); } sp_repr_css_set_property (css, "font-size",font_size.str().c_str()); if (remove) { sp_repr_css_set_property (css, "display","none"); } sp_repr_css_set_property (css, "font-size",font_size.str().c_str()); Glib::ustring css_str; sp_repr_css_write_string(css,css_str); rtext->setAttribute("style", css_str.c_str()); rtspan->setAttribute("style", css_str.c_str()); rtspan->setAttribute("transform", nullptr); sp_repr_css_attr_unref (css); if (!elemref) { rtext->addChild(rtspan, nullptr); Inkscape::GC::release(rtspan); } length = Inkscape::Util::Quantity::convert(length / doc_scale, display_unit.c_str(), unit.get_abbreviation()); if (local_locale) { setlocale (LC_NUMERIC, ""); } else { setlocale (LC_NUMERIC, "C"); } gchar length_str[64]; bool x100 = false; if (smallx100 && length < 1 ) { length *=100; x100 = true; g_snprintf(length_str, 64, "%.*f", (int)precision - 2, length); } else { g_snprintf(length_str, 64, "%.*f", (int)precision, length); } setlocale (LC_NUMERIC, locale_base); gchar * format_str = format.param_getSVGValue(); Glib::ustring label_value(format_str); g_free(format_str); size_t s = label_value.find(Glib::ustring("{measure}"),0); if(s < label_value.length()) { label_value.replace(s,s+9,length_str); } s = label_value.find(Glib::ustring("{unit}"),0); if(s < label_value.length()) { if (x100) { label_value.replace(s,s+6,""); } else { label_value.replace(s,s+6,unit.get_abbreviation()); } } if (showindex) { label_value = Glib::ustring("[") + Glib::ustring::format(counter) + Glib::ustring("] ") + label_value; } if ( !valid ) { label_value = Glib::ustring(_("Non Uniform Scale")); } Inkscape::XML::Node *rstring = nullptr; if (!elemref) { rstring = xml_doc->createTextNode(label_value.c_str()); rtspan->addChild(rstring, nullptr); Inkscape::GC::release(rstring); } else { rstring = rtspan->firstChild(); rstring->setContent(label_value.c_str()); } if (!elemref) { elemref = document->getRoot()->appendChildRepr(rtext); Inkscape::GC::release(rtext); } Geom::OptRect bounds = SP_ITEM(elemref)->bounds(SPItem::GEOMETRIC_BBOX); if (bounds) { anotation_width = bounds->width() * 1.15; rtspan->setAttribute("style", nullptr); } gchar * transform; if (rotate_anotation) { Geom::Affine affine = Geom::Affine(Geom::Translate(pos).inverse()); angle = std::fmod(angle, 2*M_PI); if (angle < 0) angle += 2*M_PI; if (angle >= rad_from_deg(90) && angle < rad_from_deg(270)) { angle = std::fmod(angle + rad_from_deg(180), 2*M_PI); if (angle < 0) angle += 2*M_PI; } affine *= Geom::Rotate(angle); affine *= Geom::Translate(pos); transform = sp_svg_transform_write(affine); } else { transform = nullptr; } rtext->setAttribute("transform", transform); g_free(transform); } void LPEMeasureSegments::createLine(Geom::Point start,Geom::Point end, Glib::ustring name, size_t counter, bool main, bool remove, bool arrows) { SPDocument * document = SP_ACTIVE_DOCUMENT; if (!document || !sp_lpe_item || !sp_lpe_item->getId()) { return; } Glib::ustring lpobjid = this->lpeobj->getId(); Glib::ustring itemid = sp_lpe_item->getId(); Glib::ustring id = name; id += Glib::ustring::format(counter); id += "-"; id += lpobjid; Inkscape::XML::Document *xml_doc = document->getReprDoc(); SPObject *elemref = document->getObjectById(id.c_str()); Inkscape::XML::Node *line = nullptr; if (!main) { Geom::Ray ray(start, end); Geom::Coord angle = ray.angle(); start = start + Point::polar(angle, helpline_distance ); end = end + Point::polar(angle, helpline_overlap ); } Geom::PathVector line_pathv; double k = (Geom::distance(start,end)/2.0) - (anotation_width/10.0) - (anotation_width/2.0); if (main && std::abs(text_top_bottom) < fontsize/1.5 && hide_back && k > 0) { //k = std::max(k , arrow_gap -1); Geom::Ray ray(end, start); Geom::Coord angle = ray.angle(); Geom::Path line_path(start); line_path.appendNew(start - Point::polar(angle, k)); line_pathv.push_back(line_path); line_path.clear(); line_path.start(end + Point::polar(angle, k)); line_path.appendNew(end); line_pathv.push_back(line_path); } else { Geom::Path line_path(start); line_path.appendNew(end); line_pathv.push_back(line_path); } if (elemref) { line = elemref->getRepr(); gchar * line_str = sp_svg_write_path( line_pathv ); line->setAttribute("d" , line_str); line->setAttribute("transform", nullptr); g_free(line_str); } else { line = xml_doc->createElement("svg:path"); line->setAttribute("id", id.c_str()); if (main) { Glib::ustring classlinedim = itemid; classlinedim += " "; classlinedim += lpobjid; classlinedim += " measure-DIM-lines measure-lines"; line->setAttribute("class", classlinedim.c_str()); } else { Glib::ustring classlinehelper = itemid; classlinehelper += " "; classlinehelper += lpobjid; classlinehelper += " measure-helper-lines measure-lines"; line->setAttribute("class", classlinehelper.c_str()); } gchar * line_str = sp_svg_write_path( line_pathv ); line->setAttribute("d" , line_str); g_free(line_str); } line->setAttribute("sodipodi:insensitive", "true"); line_pathv.clear(); Glib::ustring style; if (remove) { style ="display:none;"; } if (main) { line->setAttribute("inkscape:label", "dinline"); if (!hide_arrows) { if (arrows_outside) { style += "marker-start:url(#ArrowDINout-start);marker-end:url(#ArrowDINout-end);"; } else { style += "marker-start:url(#ArrowDIN-start);marker-end:url(#ArrowDIN-end);"; } } } else { line->setAttribute("inkscape:label", "dinhelpline"); } std::stringstream stroke_w; setlocale (LC_NUMERIC, "C"); double stroke_width = Inkscape::Util::Quantity::convert(line_width / doc_scale, "mm", display_unit.c_str()); stroke_w << stroke_width; setlocale (LC_NUMERIC, locale_base); style += "stroke-width:"; style += stroke_w.str(); gchar c[32]; sprintf(c, "#%06x", rgb32 >> 8); style += ";stroke:"; style += Glib::ustring(c); Inkscape::SVGOStringStream os; os << SP_RGBA32_A_F(coloropacity.get_value()); style = style + Glib::ustring(";stroke-opacity:") + Glib::ustring(os.str()); SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_attr_add_from_string(css, style.c_str()); Glib::ustring css_str; sp_repr_css_write_string(css,css_str); line->setAttribute("style", css_str.c_str()); if (!elemref) { elemref = document->getRoot()->appendChildRepr(line); Inkscape::GC::release(line); } } void LPEMeasureSegments::doOnApply(SPLPEItem const* lpeitem) { if (!SP_IS_SHAPE(lpeitem)) { g_warning("LPE measure line can only be applied to shapes (not groups)."); SPLPEItem * item = const_cast(lpeitem); item->removeCurrentPathEffect(false); return; } SPDocument *document = SP_ACTIVE_DOCUMENT; bool saved = DocumentUndo::getUndoSensitive(document); DocumentUndo::setUndoSensitive(document, false); Inkscape::XML::Node *styleNode = nullptr; Inkscape::XML::Node* textNode = nullptr; Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot(); for (unsigned i = 0; i < root->childCount(); ++i) { if (Glib::ustring(root->nthChild(i)->name()) == "svg:style") { styleNode = root->nthChild(i); for (unsigned j = 0; j < styleNode->childCount(); ++j) { if (styleNode->nthChild(j)->type() == Inkscape::XML::TEXT_NODE) { textNode = styleNode->nthChild(j); } } if (textNode == nullptr) { // Style element found but does not contain text node! std::cerr << "StyleDialog::_getStyleTextNode(): No text node!" << std::endl; textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode(""); styleNode->appendChild(textNode); Inkscape::GC::release(textNode); } } } if (styleNode == nullptr) { // Style element not found, create one styleNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createElement("svg:style"); textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode(""); styleNode->appendChild(textNode); Inkscape::GC::release(textNode); root->addChild(styleNode, nullptr); Inkscape::GC::release(styleNode); } Glib::ustring styleContent = Glib::ustring(textNode->content()); if (styleContent.find(".measure-arrows\n{\n") == -1) { styleContent = styleContent + Glib::ustring("\n.measure-arrows") + Glib::ustring("\n{\n}"); styleContent = styleContent + Glib::ustring("\n.measure-labels") + Glib::ustring("\n{\nline-height:125%;\nletter-spacing:0;\nword-spacing:0;\ntext-align:center;\ntext-anchor:middle;\nstroke:none;\n}"); styleContent = styleContent + Glib::ustring("\n.measure-lines") + Glib::ustring("\n{\n}"); textNode->setContent(styleContent.c_str()); } DocumentUndo::setUndoSensitive(document, saved); } Geom::PathVector LPEMeasureSegments::doEffect_path (Geom::PathVector const & path_in) { return path_in; } bool LPEMeasureSegments::isWhitelist (size_t i, std::string listsegments, bool whitelist) { size_t s = listsegments.find(std::to_string(i) + std::string(","), 0); if (s != std::string::npos) { if (whitelist) { return true; } else { return false; } } else { if (whitelist) { return false; } else { return true; } } return false; } double getAngle(Geom::Point p1, Geom::Point p2, Geom::Point p3, bool flip_side, double fix_overlaps) { Geom::Ray ray_1(p2,p1); Geom::Ray ray_2(p3,p1); bool ccw_toggle = cross(p1 - p2, p3 - p2) < 0; double angle = angle_between(ray_1, ray_2, ccw_toggle); if (Geom::deg_from_rad(angle) < fix_overlaps || Geom::deg_from_rad(angle) > 180 || ((ccw_toggle && flip_side) || (!ccw_toggle && !flip_side))) { angle = 0; } return angle; } std::vector< Point > transformNodes(std::vector< Point > nodes, Geom::Affine transform) { std::vector< Point > result; for (auto & node : nodes) { Geom::Point point = node; result.push_back(point * transform); } return result; } std::vector< Point > getNodes(SPItem * item, Geom::Affine transform, bool onbbox, bool centers, bool bboxonly) { std::vector< Point > current_nodes; SPShape * shape = dynamic_cast (item); SPText * text = dynamic_cast (item); SPGroup * group = dynamic_cast (item); SPFlowtext * flowtext = dynamic_cast (item); //TODO handle clones/use if (group) { std::vector const item_list = sp_item_group_item_list(group); for (auto sub_item : item_list) { std::vector< Point > nodes = transformNodes(getNodes(sub_item, sub_item->transform, onbbox, centers, bboxonly), transform); current_nodes.insert(current_nodes.end(), nodes.begin(), nodes.end()); } } else if (shape && !bboxonly) { SPCurve * c = shape->getCurve(); current_nodes = transformNodes(c->get_pathvector().nodes(), transform); c->unref(); } else if ((text || flowtext) && !bboxonly) { Inkscape::Text::Layout::iterator iter = te_get_layout(item)->begin(); do { Inkscape::Text::Layout::iterator iter_next = iter; iter_next.nextGlyph(); // iter_next is one glyph ahead from iter if (iter == iter_next) { break; } // get path from iter to iter_next: SPCurve *curve = te_get_layout(item)->convertToCurves(iter, iter_next); iter = iter_next; // shift to next glyph if (!curve) { continue; // error converting this glyph } if (curve->is_empty()) { // whitespace glyph? curve->unref(); continue; } std::vector< Point > letter_nodes = transformNodes(curve->get_pathvector().nodes(), transform); current_nodes.insert(current_nodes.end(),letter_nodes.begin(),letter_nodes.end()); if (iter == te_get_layout(item)->end()) { break; } } while (true); } else { onbbox = true; } if (onbbox || centers) { Geom::OptRect bbox = item->geometricBounds(item->transform); if (bbox && onbbox) { current_nodes.push_back((*bbox).corner(0) * transform); current_nodes.push_back((*bbox).corner(1) * transform); current_nodes.push_back((*bbox).corner(2) * transform); current_nodes.push_back((*bbox).corner(3) * transform); } if (bbox && centers) { current_nodes.push_back((*bbox).midpoint() * transform); } } return current_nodes; } bool sortPoints (Geom::Point a,Geom::Point b) { return (a[Geom::Y] < b[Geom::Y]); } void LPEMeasureSegments::doBeforeEffect (SPLPEItem const* lpeitem) { SPLPEItem * splpeitem = const_cast(lpeitem); Glib::ustring lpobjid = this->lpeobj->getId(); SPDocument * document = SP_ACTIVE_DOCUMENT; if (!document) { return; } //Avoid crashes on previews Inkscape::XML::Node *root = splpeitem->document->getReprRoot(); Inkscape::XML::Node *root_origin = document->getReprRoot(); if (root_origin != root) { return; } Geom::Affine parentaffinetransform = i2anc_affine(SP_OBJECT(lpeitem->parent), SP_OBJECT(document->getRoot())); Geom::Affine affinetransform = i2anc_affine(SP_OBJECT(lpeitem), SP_OBJECT(document->getRoot())); //Projection prepare Geom::PathVector pathvector; std::vector< Point > nodes; if (active_projection) { Geom::OptRect bbox = sp_lpe_item->geometricBounds(); Geom::Point pojpoint = Geom::Point(); if (bbox) { Geom::Point mid = bbox->midpoint(); double angle = Geom::rad_from_deg(angle_projection); Geom::Affine transform = affinetransform; transform *= Geom::Translate(mid).inverse(); transform *= Geom::Rotate(angle).inverse(); transform *= Geom::Translate(mid); std::vector< Point > current_nodes = getNodes(splpeitem, transform, onbbox, centers, bboxonly); nodes.insert(nodes.end(),current_nodes.begin(), current_nodes.end()); std::vector result; Geom::OptRect bbox = sp_lpe_item->geometricBounds(sp_lpe_item->transform); Geom::Point pojpoint = Geom::Point(); double maxdistance = -std::numeric_limits::max(); for (auto & node : nodes) { Geom::Point point = node; point *= Geom::Translate(mid).inverse(); point *= Geom::Rotate(angle).inverse(); point *= Geom::Translate(mid); if (point[Geom::X] > maxdistance) { maxdistance = point[Geom::X]; } } for (auto & iter : linked_items._vector) { SPObject *obj; if (iter->ref.isAttached() && iter->actived && (obj = iter->ref.getObject()) && SP_IS_ITEM(obj)) { SPItem * item = dynamic_cast(obj); if (item) { Geom::Affine affinetransform_sub = i2anc_affine(SP_OBJECT(item), SP_OBJECT(document->getRoot())); Geom::Affine transform = affinetransform_sub ; transform *= Geom::Translate(-mid); transform *= Geom::Rotate(angle).inverse(); transform *= Geom::Translate(mid); std::vector< Point > current_nodes = getNodes(item, transform, onbbox, centers, bboxonly); nodes.insert(nodes.end(),current_nodes.begin(), current_nodes.end()); } } } for (auto & node : nodes) { Geom::Point point = node; double dproj = Inkscape::Util::Quantity::convert(distance_projection, display_unit.c_str(), unit.get_abbreviation()); Geom::Coord xpos = maxdistance + dproj; result.emplace_back(xpos, point[Geom::Y]); } std::sort (result.begin(), result.end(), sortPoints); Geom::Path path; Geom::Point prevpoint(0,0); size_t counter = 0; bool started = false; for (auto & iter : result) { Geom::Point point = iter; if (Geom::are_near(prevpoint, point)){ continue; } if (!started) { path.setInitial(point); started = true; } else { if (!maxmin) { path.appendNew(point); } } prevpoint = point; } if (maxmin) { path.appendNew(result[result.size()-1]); } pathvector.push_back(path); pathvector *= Geom::Translate(-mid); pathvector *= Geom::Rotate(angle); pathvector *= Geom::Translate(mid); } } //end projection prepare SPShape *shape = dynamic_cast(splpeitem); if (shape) { //only check constrain viewbox on X Geom::Scale scaledoc = document->getDocumentScale(); display_unit = document->getDisplayUnit()->abbr.c_str(); doc_scale = Inkscape::Util::Quantity::convert( scaledoc[Geom::X], "px", display_unit.c_str() ); if (doc_scale > 0) { doc_scale= 1.0/doc_scale; } else { doc_scale = 1.0; } guint32 color32 = coloropacity.get_value(); bool colorchanged = false; if (color32 != rgb32) { colorchanged = true; } rgb32 = color32; SPCurve * c = nullptr; gchar * fontbutton_str = fontbutton.param_getSVGValue(); Glib::ustring fontdesc_ustring = Glib::ustring(fontbutton_str); Pango::FontDescription fontdesc(fontdesc_ustring); fontsize = fontdesc.get_size()/(double)Pango::SCALE; fontsize *= document->getRoot()->c2p.inverse().expansionX(); g_free(fontbutton_str); fontsize *= document->getRoot()->c2p.inverse().expansionX(); c = shape->getCurve(); Geom::Point prev_stored = Geom::Point(0,0); Geom::Point start_stored = Geom::Point(0,0); Geom::Point end_stored = Geom::Point(0,0); Geom::Point next_stored = Geom::Point(0,0); if (!active_projection) { pathvector = pathv_to_linear_and_cubic_beziers(c->get_pathvector()); pathvector *= affinetransform; } c->unref(); gchar * format_str = format.param_getSVGValue(); if (Glib::ustring(format_str).empty()) { format.param_setValue(Glib::ustring("{measure}{unit}")); } g_free(format_str); size_t ncurves = pathvector.curveCount(); items.clear(); double start_angle_cross = 0; double end_angle_cross = 0; gint counter = -1; bool previous_fix_overlaps = true; for (size_t i = 0; i < pathvector.size(); i++) { size_t count = pathvector[i].size_default(); if ( pathvector[i].closed()) { const Geom::Curve &closingline = pathvector[i].back_closed(); if (are_near(closingline.initialPoint(), closingline.finalPoint())) { count = pathvector[i].size_open(); } } for (size_t j = 0; j < count; j++) { counter++; gint fix_overlaps_degree = fix_overlaps; Geom::Point prev = Geom::Point(0,0); if (j == 0 && pathvector[i].closed()) { prev = pathvector.pointAt(pathvector[i].size() - 1); } else if (j != 0) { prev = pathvector[i].pointAt(j - 1); } Geom::Point start = pathvector[i].pointAt(j); Geom::Point end = pathvector[i].pointAt(j + 1); Geom::Point next = Geom::Point(0,0); if (pathvector[i].closed() && pathvector[i].size() == j+1){ end = pathvector[i].pointAt(0); next = pathvector[i].pointAt(1); } else if (pathvector[i].size() > j + 1) { next = pathvector[i].pointAt(j+2); } gchar * blacklist_str = blacklist.param_getSVGValue(); std::string listsegments(std::string(blacklist_str) + std::string(",")); listsegments.erase(std::remove(listsegments.begin(), listsegments.end(), ' '), listsegments.end()); g_free(blacklist_str); if (isWhitelist(counter, listsegments, (bool)whitelist) && !Geom::are_near(start, end)) { Glib::ustring idprev = Glib::ustring("infoline-on-start-"); idprev += Glib::ustring::format(counter-1); idprev += "-"; idprev += lpobjid; SPObject *elemref = document->getObjectById(idprev.c_str()); if (elemref){ SPPath* path = dynamic_cast(elemref); if (path) { SPCurve* prevcurve = path->getCurve(); if (prevcurve) { prev_stored = *prevcurve->first_point(); } prevcurve->unref(); } } Glib::ustring idstart = Glib::ustring("infoline-on-start-"); idstart += Glib::ustring::format(counter); idstart += "-"; idstart += lpobjid; elemref = document->getObjectById(idstart.c_str()); if (elemref) { SPPath* path = dynamic_cast(elemref); if (path) { SPCurve* startcurve = path->getCurve(); if (startcurve) { start_stored = *startcurve->first_point(); } startcurve->unref(); } } Glib::ustring idend = Glib::ustring("infoline-on-end-"); idend += Glib::ustring::format(counter); idend += "-"; idend += lpobjid; elemref = document->getObjectById(idend.c_str()); if (elemref) { SPPath* path = dynamic_cast(elemref); if (path) { SPCurve* endcurve = path->getCurve(); if (endcurve) { end_stored = *endcurve->first_point(); } endcurve->unref(); } } Glib::ustring idnext = Glib::ustring("infoline-on-start-"); idnext += Glib::ustring::format(counter+1); idnext += "-"; idnext += lpobjid; elemref = document->getObjectById(idnext.c_str()); if (elemref) { SPPath* path = dynamic_cast(elemref); if (path) { SPCurve* nextcurve = path->getCurve(); if (nextcurve) { next_stored = *nextcurve->first_point(); } nextcurve->unref(); } } Glib::ustring infoline_on_start = "infoline-on-start-"; infoline_on_start += Glib::ustring::format(counter); infoline_on_start += "-"; infoline_on_start += lpobjid; Glib::ustring infoline_on_end = "infoline-on-end-"; infoline_on_end += Glib::ustring::format(counter); infoline_on_end += "-"; infoline_on_end += lpobjid; Glib::ustring infoline = "infoline-"; infoline += Glib::ustring::format(counter); infoline += "-"; infoline += lpobjid; Glib::ustring texton = "text-on-"; texton += Glib::ustring::format(counter); texton += "-"; texton += lpobjid; items.push_back(infoline_on_start); items.push_back(infoline_on_end); items.push_back(infoline); items.push_back(texton); if (!hide_arrows) { if (arrows_outside) { items.emplace_back("ArrowDINout-start"); items.emplace_back("ArrowDINout-end"); } else { items.emplace_back("ArrowDIN-start"); items.emplace_back("ArrowDIN-end"); } } if (((Geom::are_near(prev, prev_stored, 0.01) && Geom::are_near(next, next_stored, 0.01)) || fix_overlaps_degree == 180) && Geom::are_near(start, start_stored, 0.01) && Geom::are_near(end, end_stored, 0.01) && !this->upd_params && !colorchanged) { continue; } Geom::Point hstart = start; Geom::Point hend = end; bool remove = false; if (orientation == OM_VERTICAL) { Coord xpos = std::max(hstart[Geom::X],hend[Geom::X]); if (flip_side) { xpos = std::min(hstart[Geom::X],hend[Geom::X]); } hstart[Geom::X] = xpos; hend[Geom::X] = xpos; if (hstart[Geom::Y] > hend[Geom::Y]) { swap(hstart,hend); swap(start,end); } if (Geom::are_near(hstart[Geom::Y], hend[Geom::Y])) { remove = true; } } else if (orientation == OM_HORIZONTAL) { Coord ypos = std::max(hstart[Geom::Y],hend[Geom::Y]); if (flip_side) { ypos = std::min(hstart[Geom::Y],hend[Geom::Y]); } hstart[Geom::Y] = ypos; hend[Geom::Y] = ypos; if (hstart[Geom::X] < hend[Geom::X]) { swap(hstart,hend); swap(start,end); } if (Geom::are_near(hstart[Geom::X], hend[Geom::X])) { remove = true; } } else if (fix_overlaps_degree != 180) { start_angle_cross = getAngle( start, prev, end, flip_side, fix_overlaps_degree); if (prev == Geom::Point(0,0)) { start_angle_cross = 0; } end_angle_cross = getAngle(end, start, next, flip_side, fix_overlaps_degree); if (next == Geom::Point(0,0)) { end_angle_cross = 0; } } if (remove) { createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-"), counter, true, true, true); createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-on-start-"), counter, true, true, true); createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-on-end-"), counter, true, true, true); createTextLabel(Geom::Point(), counter, 0, 0, true, true); continue; } Geom::Ray ray(hstart,hend); Geom::Coord angle = ray.angle(); if (flip_side) { angle = std::fmod(angle + rad_from_deg(180), 2*M_PI); if (angle < 0) angle += 2*M_PI; } Geom::Coord angle_cross = std::fmod(angle + rad_from_deg(90), 2*M_PI); if (angle_cross < 0) angle_cross += 2*M_PI; angle = std::fmod(angle, 2*M_PI); if (angle < 0) angle += 2*M_PI; double turn = Geom::rad_from_deg(-90); if (flip_side) { end_angle_cross *= -1; start_angle_cross *= -1; //turn *= -1; } double position_turned_start = position / sin(start_angle_cross/2.0); double length = Geom::distance(start,end); if (fix_overlaps_degree != 180 && start_angle_cross != 0 && position_turned_start < length && previous_fix_overlaps) { hstart = hstart - Point::polar(angle_cross - (start_angle_cross/2.0) - turn, position_turned_start); } else { hstart = hstart - Point::polar(angle_cross, position); } createLine(start, hstart, Glib::ustring("infoline-on-start-"), counter, false, false); double position_turned_end = position / sin(end_angle_cross/2.0); double endlength = Geom::distance(end,next); if (fix_overlaps_degree != 180 && end_angle_cross != 0 && position_turned_end < length && position_turned_end < endlength) { hend = hend - Point::polar(angle_cross + (end_angle_cross/2.0) + turn, position_turned_end); previous_fix_overlaps = true; } else { hend = hend - Point::polar(angle_cross, position); previous_fix_overlaps = false; } length = Geom::distance(start,end) * scale; Geom::Point pos = Geom::middle_point(hstart,hend); if (!hide_arrows) { if (arrows_outside) { createArrowMarker(Glib::ustring("ArrowDINout-start")); createArrowMarker(Glib::ustring("ArrowDINout-end")); } else { createArrowMarker(Glib::ustring("ArrowDIN-start")); createArrowMarker(Glib::ustring("ArrowDIN-end")); } } if (angle >= rad_from_deg(90) && angle < rad_from_deg(270)) { pos = pos - Point::polar(angle_cross, text_top_bottom + (fontsize/2.5)); } else { pos = pos + Point::polar(angle_cross, text_top_bottom + (fontsize/2.5)); } double parents_scale = (parentaffinetransform.expansionX() + parentaffinetransform.expansionY()) / 2.0; if (!scale_sensitive) { length /= parents_scale; } if ((anotation_width/2) > Geom::distance(hstart,hend)/2.0) { if (avoid_overlapping) { pos = pos - Point::polar(angle_cross, position + (anotation_width/2.0)); angle += Geom::rad_from_deg(90); } else { pos = pos - Point::polar(angle_cross, position); } } if (!scale_sensitive && !parentaffinetransform.preservesAngles()) { createTextLabel(pos, counter, length, angle, remove, false); } else { createTextLabel(pos, counter, length, angle, remove, true); } arrow_gap = 8 * Inkscape::Util::Quantity::convert(line_width / doc_scale, "mm", display_unit.c_str()); SPCSSAttr *css = sp_repr_css_attr_new(); setlocale (LC_NUMERIC, "C"); double width_line = atof(sp_repr_css_property(css,"stroke-width","-1")); setlocale (LC_NUMERIC, locale_base); if (width_line > -0.0001) { arrow_gap = 8 * Inkscape::Util::Quantity::convert(width_line/ doc_scale, "mm", display_unit.c_str()); } if(flip_side) { arrow_gap *= -1; } if(hide_arrows) { arrow_gap *= 0; } createLine(end, hend, Glib::ustring("infoline-on-end-"), counter, false, false); if (!arrows_outside) { hstart = hstart + Point::polar(angle, arrow_gap); hend = hend - Point::polar(angle, arrow_gap ); } if ((Geom::distance(hstart,hend)/2.0) > (anotation_width/1.9) + arrow_gap) { createLine(hstart, hend, Glib::ustring("infoline-"), counter, true, false, true); } else { createLine(hstart, hend, Glib::ustring("infoline-"), counter, true, true, true); } } else { createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-"), counter, true, true, true); createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-on-start-"), counter, true, true, true); createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-on-end-"), counter, true, true, true); createTextLabel(Geom::Point(), counter, 0, 0, true, true); } } } if (previous_size) { for (size_t counter = ncurves; counter < previous_size; counter++) { createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-"), counter, true, true, true); createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-on-start-"), counter, true, true, true); createLine(Geom::Point(), Geom::Point(), Glib::ustring("infoline-on-end-"), counter, true, true, true); createTextLabel(Geom::Point(), counter, 0, 0, true, true); } } previous_size = ncurves; } } void LPEMeasureSegments::doOnVisibilityToggled(SPLPEItem const* /*lpeitem*/) { processObjects(LPE_VISIBILITY); } void LPEMeasureSegments::doOnRemove (SPLPEItem const* /*lpeitem*/) { //set "keep paths" hook on sp-lpe-item.cpp if (keep_paths) { processObjects(LPE_TO_OBJECTS); items.clear(); return; } processObjects(LPE_ERASE); } }; //namespace LivePathEffect }; /* namespace Inkscape */ /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offset:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :