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