1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3 * LPE <ruler> implementation, see lpe-ruler.cpp.
4 */
5
6 /*
7 * Authors:
8 * Maximilian Albert
9 *
10 * Copyright (C) Maximilian Albert 2008 <maximilian.albert@gmail.com>
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
15 #include "live_effects/lpe-ruler.h"
16 // TODO due to internal breakage in glibmm headers, this must be last:
17 #include <glibmm/i18n.h>
18
19 namespace Inkscape {
20 namespace LivePathEffect {
21
22 static const Util::EnumData<MarkDirType> MarkDirData[] = {
23 {MARKDIR_LEFT , N_("Left"), "left"},
24 {MARKDIR_RIGHT , N_("Right"), "right"},
25 {MARKDIR_BOTH , N_("Both"), "both"},
26 };
27 static const Util::EnumDataConverter<MarkDirType> MarkDirTypeConverter(MarkDirData, sizeof(MarkDirData)/sizeof(*MarkDirData));
28
29 static const Util::EnumData<BorderMarkType> BorderMarkData[] = {
30 {BORDERMARK_NONE , NC_("Border mark", "None"), "none"},
31 {BORDERMARK_START , N_("Start"), "start"},
32 {BORDERMARK_END , N_("End"), "end"},
33 {BORDERMARK_BOTH , N_("Both"), "both"},
34 };
35 static const Util::EnumDataConverter<BorderMarkType> BorderMarkTypeConverter(BorderMarkData, sizeof(BorderMarkData)/sizeof(*BorderMarkData));
36
LPERuler(LivePathEffectObject * lpeobject)37 LPERuler::LPERuler(LivePathEffectObject *lpeobject) :
38 Effect(lpeobject),
39 mark_distance(_("_Mark distance:"), _("Distance between successive ruler marks"), "mark_distance", &wr, this, 20.0),
40 unit(_("Unit:"), _("Unit"), "unit", &wr, this),
41 mark_length(_("Ma_jor length:"), _("Length of major ruler marks"), "mark_length", &wr, this, 14.0),
42 minor_mark_length(_("Mino_r length:"), _("Length of minor ruler marks"), "minor_mark_length", &wr, this, 7.0),
43 major_mark_steps(_("Major steps_:"), _("Draw a major mark every ... steps"), "major_mark_steps", &wr, this, 5),
44 shift(_("Shift marks _by:"), _("Shift marks by this many steps"), "shift", &wr, this, 0),
45 mark_dir(_("Mark direction:"), _("Direction of marks (when viewing along the path from start to end)"), "mark_dir", MarkDirTypeConverter, &wr, this, MARKDIR_LEFT),
46 offset(_("_Offset:"), _("Offset of first mark"), "offset", &wr, this, 0.0),
47 border_marks(_("Border marks:"), _("Choose whether to draw marks at the beginning and end of the path"), "border_marks", BorderMarkTypeConverter, &wr, this, BORDERMARK_BOTH)
48 {
49 registerParameter(&unit);
50 registerParameter(&mark_distance);
51 registerParameter(&mark_length);
52 registerParameter(&minor_mark_length);
53 registerParameter(&major_mark_steps);
54 registerParameter(&shift);
55 registerParameter(&offset);
56 registerParameter(&mark_dir);
57 registerParameter(&border_marks);
58
59 major_mark_steps.param_make_integer();
60 major_mark_steps.param_set_range(1, 1000);
61 shift.param_make_integer();
62
63 mark_length.param_set_increments(1.0, 10.0);
64 minor_mark_length.param_set_increments(1.0, 10.0);
65 offset.param_set_increments(1.0, 10.0);
66 }
67
68 LPERuler::~LPERuler()
69 = default;
70
71 Geom::Point LPERuler::n_major;
72 Geom::Point LPERuler::n_minor;
73
74 Geom::Piecewise<Geom::D2<Geom::SBasis> >
ruler_mark(Geom::Point const & A,Geom::Point const & n,MarkType const & marktype)75 LPERuler::ruler_mark(Geom::Point const &A, Geom::Point const &n, MarkType const &marktype)
76 {
77 using namespace Geom;
78
79 double real_mark_length = mark_length;
80 SPDocument *document = getSPDoc();
81 if (document) {
82 real_mark_length = Inkscape::Util::Quantity::convert(real_mark_length, unit.get_abbreviation(), document->getDisplayUnit()->abbr.c_str());
83 }
84 double real_minor_mark_length = minor_mark_length;
85 if (document) {
86 real_minor_mark_length = Inkscape::Util::Quantity::convert(real_minor_mark_length, unit.get_abbreviation(), document->getDisplayUnit()->abbr.c_str());
87 }
88 n_major = real_mark_length * n;
89 n_minor = real_minor_mark_length * n;
90 if (mark_dir == MARKDIR_BOTH) {
91 n_major = n_major * 0.5;
92 n_minor = n_minor * 0.5;
93 }
94
95 Point C, D;
96 switch (marktype) {
97 case MARK_MAJOR:
98 C = A;
99 D = A + n_major;
100 if (mark_dir == MARKDIR_BOTH)
101 C -= n_major;
102 break;
103 case MARK_MINOR:
104 C = A;
105 D = A + n_minor;
106 if (mark_dir == MARKDIR_BOTH)
107 C -= n_minor;
108 break;
109 default:
110 // do nothing
111 break;
112 }
113
114 Piecewise<D2<SBasis> > seg(D2<SBasis>(SBasis(C[X], D[X]), SBasis(C[Y], D[Y])));
115 return seg;
116 }
117
118 Geom::Piecewise<Geom::D2<Geom::SBasis> >
doEffect_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis>> const & pwd2_in)119 LPERuler::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
120 {
121 using namespace Geom;
122
123 const int mminterval = static_cast<int>(major_mark_steps);
124 const int i_shift = static_cast<int>(shift) % mminterval;
125 int sign = (mark_dir == MARKDIR_RIGHT ? 1 : -1 );
126
127 Piecewise<D2<SBasis> >output(pwd2_in);
128 Piecewise<D2<SBasis> >speed = derivative(pwd2_in);
129 Piecewise<SBasis> arclength = arcLengthSb(pwd2_in);
130 double totlength = arclength.lastValue();
131
132 //find at which times to draw a mark:
133 std::vector<double> s_cuts;
134
135 double real_mark_distance = mark_distance;
136 SPDocument *document = getSPDoc();
137 if (document) {
138 real_mark_distance = Inkscape::Util::Quantity::convert(real_mark_distance, unit.get_abbreviation(), document->getDisplayUnit()->abbr.c_str());
139 }
140 double real_offset = offset;
141 if (document) {
142 real_offset = Inkscape::Util::Quantity::convert(real_offset, unit.get_abbreviation(), document->getDisplayUnit()->abbr.c_str());
143 }
144 for (double s = real_offset; s<totlength; s+=real_mark_distance){
145 s_cuts.push_back(s);
146 }
147 std::vector<std::vector<double> > roots = multi_roots(arclength, s_cuts);
148 std::vector<double> t_cuts;
149 for (auto & root : roots){
150 //FIXME: 2geom multi_roots solver seem to sometimes "repeat" solutions.
151 //Here, we are supposed to have one and only one solution for each s.
152 if(root.size()>0)
153 t_cuts.push_back(root[0]);
154 }
155 //draw the marks
156 for (size_t i = 0; i < t_cuts.size(); i++) {
157 Point A = pwd2_in(t_cuts[i]);
158 Point n = rot90(unit_vector(speed(t_cuts[i])))*sign;
159 if (static_cast<int>(i % mminterval) == i_shift) {
160 output.concat (ruler_mark(A, n, MARK_MAJOR));
161 } else {
162 output.concat (ruler_mark(A, n, MARK_MINOR));
163 }
164 }
165 //eventually draw a mark at start
166 if ((border_marks == BORDERMARK_START || border_marks == BORDERMARK_BOTH) && (offset != 0.0 || i_shift != 0)){
167 Point A = pwd2_in.firstValue();
168 Point n = rot90(unit_vector(speed.firstValue()))*sign;
169 output.concat (ruler_mark(A, n, MARK_MAJOR));
170 }
171 //eventually draw a mark at end
172 if (border_marks == BORDERMARK_END || border_marks == BORDERMARK_BOTH){
173 Point A = pwd2_in.lastValue();
174 Point n = rot90(unit_vector(speed.lastValue()))*sign;
175 //speed.lastValue() is sometimes wrong when the path is closed: a tiny line seg might added at the end to fix rounding errors...
176 //TODO: Find a better fix!! (How do we know if the path was closed?)
177 if ( A == pwd2_in.firstValue() &&
178 speed.segs.size() > 1 &&
179 speed.segs.back()[X].size() <= 1 &&
180 speed.segs.back()[Y].size() <= 1 &&
181 speed.segs.back()[X].tailError(0) <= 1e-10 &&
182 speed.segs.back()[Y].tailError(0) <= 1e-10
183 ){
184 n = rot90(unit_vector(speed.segs[speed.segs.size()-2].at1()))*sign;
185 }
186 output.concat (ruler_mark(A, n, MARK_MAJOR));
187 }
188
189 return output;
190 }
191
192 } //namespace LivePathEffect
193 } /* namespace Inkscape */
194
195 /*
196 Local Variables:
197 mode:c++
198 c-file-style:"stroustrup"
199 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
200 indent-tabs-mode:nil
201 fill-column:99
202 End:
203 */
204 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
205