1 /*  GRAPHITE2 LICENSING
2 
3     Copyright 2010, SIL International
4     All rights reserved.
5 
6     This library is free software; you can redistribute it and/or modify
7     it under the terms of the GNU Lesser General Public License as published
8     by the Free Software Foundation; either version 2.1 of License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14     Lesser General Public License for more details.
15 
16     You should also have received a copy of the GNU Lesser General Public
17     License along with this library in the file named "LICENSE".
18     If not, write to the Free Software Foundation, 51 Franklin Street,
19     Suite 500, Boston, MA 02110-1335, USA or visit their web page on the
20     internet at http://www.fsf.org/licenses/lgpl.html.
21 
22 Alternatively, the contents of this file may be used under the terms of the
23 Mozilla Public License (http://mozilla.org/MPL) or the GNU General Public
24 License, as published by the Free Software Foundation, either version 2
25 of the License or (at your option) any later version.
26 */
27 #include <algorithm>
28 #include <cmath>
29 #include <limits>
30 
31 #include "inc/Intervals.h"
32 #include "inc/Segment.h"
33 #include "inc/Slot.h"
34 #include "inc/debug.h"
35 #include "inc/bits.h"
36 
37 using namespace graphite2;
38 
39 #include <cmath>
40 
41 inline
split_at(float p)42 Zones::Exclusion  Zones::Exclusion::split_at(float p) {
43     Exclusion r(*this);
44     r.xm = x = p;
45     return r;
46 }
47 
48 inline
left_trim(float p)49 void Zones::Exclusion::left_trim(float p) {
50     x = p;
51 }
52 
53 inline
operator +=(Exclusion const & rhs)54 Zones::Exclusion & Zones::Exclusion::operator += (Exclusion const & rhs) {
55     c += rhs.c; sm += rhs.sm; smx += rhs.smx; open = false;
56     return *this;
57 }
58 
59 inline
outcode(float val) const60 uint8 Zones::Exclusion::outcode(float val) const {
61     float p = val;
62     //float d = std::numeric_limits<float>::epsilon();
63     float d = 0.;
64     return ((p - xm >= d) << 1) | (x - p > d);
65 }
66 
exclude_with_margins(float xmin,float xmax,int axis)67 void Zones::exclude_with_margins(float xmin, float xmax, int axis) {
68     remove(xmin, xmax);
69     weightedAxis(axis, xmin-_margin_len, xmin, 0, 0, _margin_weight, xmin-_margin_len, 0, 0, false);
70     weightedAxis(axis, xmax, xmax+_margin_len, 0, 0, _margin_weight, xmax+_margin_len, 0, 0, false);
71 }
72 
73 namespace
74 {
75 
76 inline
separated(float a,float b)77 bool separated(float a, float b) {
78     return a != b;
79     //int exp;
80     //float res = frexpf(fabs(a - b), &exp);
81     //return (*(unsigned int *)(&res) > 4);
82     //return std::fabs(a-b) > std::numeric_limits<float>::epsilon(); // std::epsilon may not work. but 0.5 fails exising 64 bit tests
83     //return std::fabs(a-b) > 0.5f;
84 }
85 
86 }
87 
insert(Exclusion e)88 void Zones::insert(Exclusion e)
89 {
90 #if !defined GRAPHITE2_NTRACING
91     addDebug(&e);
92 #endif
93     e.x = max(e.x, _pos);
94     e.xm = min(e.xm, _posm);
95     if (e.x >= e.xm) return;
96 
97     for (iterator i = _exclusions.begin(), ie = _exclusions.end(); i != ie && e.x < e.xm; ++i)
98     {
99         const uint8 oca = e.outcode(i->x),
100                     ocb = e.outcode(i->xm);
101         if ((oca & ocb) != 0) continue;
102 
103         switch (oca ^ ocb)  // What kind of overlap?
104         {
105         case 0:     // e completely covers i
106             // split e at i.x into e1,e2
107             // split e2 at i.mx into e2,e3
108             // drop e1 ,i+e2, e=e3
109             *i += e;
110             e.left_trim(i->xm);
111             break;
112         case 1:     // e overlaps on the rhs of i
113             // split i at e->x into i1,i2
114             // split e at i.mx into e1,e2
115             // trim i1, insert i2+e1, e=e2
116             if (!separated(i->xm, e.x)) break;
117             if (separated(i->x,e.x))   { i = _exclusions.insert(i,i->split_at(e.x)); ++i; }
118             *i += e;
119             e.left_trim(i->xm);
120             break;
121         case 2:     // e overlaps on the lhs of i
122             // split e at i->x into e1,e2
123             // split i at e.mx into i1,i2
124             // drop e1, insert e2+i1, trim i2
125             if (!separated(e.xm, i->x)) return;
126             if (separated(e.xm, i->xm)) i = _exclusions.insert(i,i->split_at(e.xm));
127             *i += e;
128             return;
129         case 3:     // i completely covers e
130             // split i at e.x into i1,i2
131             // split i2 at e.mx into i2,i3
132             // insert i1, insert e+i2
133             if (separated(e.xm, i->xm)) i = _exclusions.insert(i,i->split_at(e.xm));
134             i = _exclusions.insert(i, i->split_at(e.x));
135             *++i += e;
136             return;
137         }
138 
139         ie = _exclusions.end();
140     }
141 }
142 
143 
remove(float x,float xm)144 void Zones::remove(float x, float xm)
145 {
146 #if !defined GRAPHITE2_NTRACING
147     removeDebug(x, xm);
148 #endif
149     x = max(x, _pos);
150     xm = min(xm, _posm);
151     if (x >= xm) return;
152 
153     for (iterator i = _exclusions.begin(), ie = _exclusions.end(); i != ie; ++i)
154     {
155         const uint8 oca = i->outcode(x),
156                     ocb = i->outcode(xm);
157         if ((oca & ocb) != 0)   continue;
158 
159         switch (oca ^ ocb)  // What kind of overlap?
160         {
161         case 0:     // i completely covers e
162             if (separated(i->x, x))  { i = _exclusions.insert(i,i->split_at(x)); ++i; }
163             GR_FALLTHROUGH;
164             // no break
165         case 1:     // i overlaps on the rhs of e
166             i->left_trim(xm);
167             return;
168         case 2:     // i overlaps on the lhs of e
169             i->xm = x;
170             if (separated(i->x, i->xm)) break;
171             GR_FALLTHROUGH;
172             // no break
173         case 3:     // e completely covers i
174             i = _exclusions.erase(i);
175             --i;
176             break;
177         }
178 
179         ie = _exclusions.end();
180     }
181 }
182 
183 
find_exclusion_under(float x) const184 Zones::const_iterator Zones::find_exclusion_under(float x) const
185 {
186     size_t l = 0, h = _exclusions.size();
187 
188     while (l < h)
189     {
190         size_t const p = (l+h) >> 1;
191         switch (_exclusions[p].outcode(x))
192         {
193         case 0 : return _exclusions.begin()+p;
194         case 1 : h = p; break;
195         case 2 :
196         case 3 : l = p+1; break;
197         }
198     }
199 
200     return _exclusions.begin()+l;
201 }
202 
203 
closest(float origin,float & cost) const204 float Zones::closest(float origin, float & cost) const
205 {
206     float best_c = std::numeric_limits<float>::max(),
207           best_x = 0;
208 
209     const const_iterator start = find_exclusion_under(origin);
210 
211     // Forward scan looking for lowest cost
212     for (const_iterator i = start, ie = _exclusions.end(); i != ie; ++i)
213         if (i->track_cost(best_c, best_x, origin)) break;
214 
215     // Backward scan looking for lowest cost
216     //  We start from the exclusion to the immediate left of start since we've
217     //  already tested start with the right most scan above.
218     for (const_iterator i = start-1, ie = _exclusions.begin()-1; i != ie; --i)
219         if (i->track_cost(best_c, best_x, origin)) break;
220 
221     cost = (best_c == std::numeric_limits<float>::max() ? -1 : best_c);
222     return best_x;
223 }
224 
225 
226 // Cost and test position functions
227 
track_cost(float & best_cost,float & best_pos,float origin) const228 bool Zones::Exclusion::track_cost(float & best_cost, float & best_pos, float origin) const {
229     const float p = test_position(origin),
230                 localc = cost(p - origin);
231     if (open && localc > best_cost) return true;
232 
233     if (localc < best_cost)
234     {
235         best_cost = localc;
236         best_pos = p;
237     }
238     return false;
239 }
240 
241 inline
cost(float p) const242 float Zones::Exclusion::cost(float p) const {
243     return (sm * p - 2 * smx) * p + c;
244 }
245 
246 
test_position(float origin) const247 float Zones::Exclusion::test_position(float origin) const {
248     if (sm < 0)
249     {
250         // sigh, test both ends and perhaps the middle too!
251         float res = x;
252         float cl = cost(x);
253         if (x < origin && xm > origin)
254         {
255             float co = cost(origin);
256             if (co < cl)
257             {
258                 cl = co;
259                 res = origin;
260             }
261         }
262         float cr = cost(xm);
263         return cl > cr ? xm : res;
264     }
265     else
266     {
267         float zerox = smx / sm + origin;
268         if (zerox < x) return x;
269         else if (zerox > xm) return xm;
270         else return zerox;
271     }
272 }
273 
274 
275 #if !defined GRAPHITE2_NTRACING
276 
jsonDbgOut(Segment * seg) const277 void Zones::jsonDbgOut(Segment *seg) const {
278 
279     if (_dbg)
280     {
281         for (Zones::idebugs s = dbgs_begin(), e = dbgs_end(); s != e; ++s)
282         {
283             *_dbg << json::flat << json::array
284                 << objectid(dslot(seg, (Slot *)(s->_env[0])))
285                 << reinterpret_cast<ptrdiff_t>(s->_env[1]);
286             if (s->_isdel)
287                 *_dbg << "remove" << Position(s->_excl.x, s->_excl.xm);
288             else
289                 *_dbg << "exclude" << json::flat << json::array
290                     << s->_excl.x << s->_excl.xm
291                     << s->_excl.sm << s->_excl.smx << s->_excl.c
292                     << json::close;
293             *_dbg << json::close;
294         }
295     }
296 }
297 
298 #endif
299