1 | // SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later |
2 | // Copyright 2010, SIL International, All rights reserved. |
3 | |
4 | #include <algorithm> |
5 | #include <cmath> |
6 | #include <limits> |
7 | |
8 | #include "inc/Intervals.h" |
9 | #include "inc/Segment.h" |
10 | #include "inc/Slot.h" |
11 | #include "inc/debug.h" |
12 | #include "inc/bits.h" |
13 | |
14 | using namespace graphite2; |
15 | |
16 | #include <cmath> |
17 | |
18 | inline |
19 | Zones::Exclusion Zones::Exclusion::split_at(float p) { |
20 | Exclusion r(*this); |
21 | r.xm = x = p; |
22 | return r; |
23 | } |
24 | |
25 | inline |
26 | void Zones::Exclusion::left_trim(float p) { |
27 | x = p; |
28 | } |
29 | |
30 | inline |
31 | Zones::Exclusion & Zones::Exclusion::operator += (Exclusion const & rhs) { |
32 | c += rhs.c; sm += rhs.sm; smx += rhs.smx; open = false; |
33 | return *this; |
34 | } |
35 | |
36 | inline |
37 | uint8 Zones::Exclusion::outcode(float val) const { |
38 | float p = val; |
39 | //float d = std::numeric_limits<float>::epsilon(); |
40 | float d = 0.; |
41 | return ((p - xm >= d) << 1) | (x - p > d); |
42 | } |
43 | |
44 | void Zones::exclude_with_margins(float xmin, float xmax, int axis) { |
45 | remove(xmin, xmax); |
46 | weightedAxis(axis, xmin-_margin_len, xmin, 0, 0, _margin_weight, xmin-_margin_len, 0, 0, false); |
47 | weightedAxis(axis, xmax, xmax+_margin_len, 0, 0, _margin_weight, xmax+_margin_len, 0, 0, false); |
48 | } |
49 | |
50 | namespace |
51 | { |
52 | |
53 | inline |
54 | bool separated(float a, float b) { |
55 | return a != b; |
56 | //int exp; |
57 | //float res = frexpf(fabs(a - b), &exp); |
58 | //return (*(unsigned int *)(&res) > 4); |
59 | //return std::fabs(a-b) > std::numeric_limits<float>::epsilon(); // std::epsilon may not work. but 0.5 fails exising 64 bit tests |
60 | //return std::fabs(a-b) > 0.5f; |
61 | } |
62 | |
63 | } |
64 | |
65 | void Zones::insert(Exclusion e) |
66 | { |
67 | #if !defined GRAPHITE2_NTRACING |
68 | addDebug(&e); |
69 | #endif |
70 | e.x = max(e.x, _pos); |
71 | e.xm = min(e.xm, _posm); |
72 | if (e.x >= e.xm) return; |
73 | |
74 | for (iterator i = _exclusions.begin(), ie = _exclusions.end(); i != ie && e.x < e.xm; ++i) |
75 | { |
76 | const uint8 oca = e.outcode(i->x), |
77 | ocb = e.outcode(i->xm); |
78 | if ((oca & ocb) != 0) continue; |
79 | |
80 | switch (oca ^ ocb) // What kind of overlap? |
81 | { |
82 | case 0: // e completely covers i |
83 | // split e at i.x into e1,e2 |
84 | // split e2 at i.mx into e2,e3 |
85 | // drop e1 ,i+e2, e=e3 |
86 | *i += e; |
87 | e.left_trim(i->xm); |
88 | break; |
89 | case 1: // e overlaps on the rhs of i |
90 | // split i at e->x into i1,i2 |
91 | // split e at i.mx into e1,e2 |
92 | // trim i1, insert i2+e1, e=e2 |
93 | if (!separated(i->xm, e.x)) break; |
94 | if (separated(i->x,e.x)) { i = _exclusions.insert(i,i->split_at(e.x)); ++i; } |
95 | *i += e; |
96 | e.left_trim(i->xm); |
97 | break; |
98 | case 2: // e overlaps on the lhs of i |
99 | // split e at i->x into e1,e2 |
100 | // split i at e.mx into i1,i2 |
101 | // drop e1, insert e2+i1, trim i2 |
102 | if (!separated(e.xm, i->x)) return; |
103 | if (separated(e.xm, i->xm)) i = _exclusions.insert(i,i->split_at(e.xm)); |
104 | *i += e; |
105 | return; |
106 | case 3: // i completely covers e |
107 | // split i at e.x into i1,i2 |
108 | // split i2 at e.mx into i2,i3 |
109 | // insert i1, insert e+i2 |
110 | if (separated(e.xm, i->xm)) i = _exclusions.insert(i,i->split_at(e.xm)); |
111 | i = _exclusions.insert(i, i->split_at(e.x)); |
112 | *++i += e; |
113 | return; |
114 | } |
115 | |
116 | ie = _exclusions.end(); |
117 | } |
118 | } |
119 | |
120 | |
121 | void Zones::remove(float x, float xm) |
122 | { |
123 | #if !defined GRAPHITE2_NTRACING |
124 | removeDebug(x, xm); |
125 | #endif |
126 | x = max(x, _pos); |
127 | xm = min(xm, _posm); |
128 | if (x >= xm) return; |
129 | |
130 | for (iterator i = _exclusions.begin(), ie = _exclusions.end(); i != ie; ++i) |
131 | { |
132 | const uint8 oca = i->outcode(x), |
133 | ocb = i->outcode(xm); |
134 | if ((oca & ocb) != 0) continue; |
135 | |
136 | switch (oca ^ ocb) // What kind of overlap? |
137 | { |
138 | case 0: // i completely covers e |
139 | if (separated(i->x, x)) { i = _exclusions.insert(i,i->split_at(x)); ++i; } |
140 | GR_FALLTHROUGH; |
141 | // no break |
142 | case 1: // i overlaps on the rhs of e |
143 | i->left_trim(xm); |
144 | return; |
145 | case 2: // i overlaps on the lhs of e |
146 | i->xm = x; |
147 | if (separated(i->x, i->xm)) break; |
148 | GR_FALLTHROUGH; |
149 | // no break |
150 | case 3: // e completely covers i |
151 | i = _exclusions.erase(i); |
152 | --i; |
153 | break; |
154 | } |
155 | |
156 | ie = _exclusions.end(); |
157 | } |
158 | } |
159 | |
160 | |
161 | Zones::const_iterator Zones::find_exclusion_under(float x) const |
162 | { |
163 | size_t l = 0, h = _exclusions.size(); |
164 | |
165 | while (l < h) |
166 | { |
167 | size_t const p = (l+h) >> 1; |
168 | switch (_exclusions[p].outcode(x)) |
169 | { |
170 | case 0 : return _exclusions.begin()+p; |
171 | case 1 : h = p; break; |
172 | case 2 : |
173 | case 3 : l = p+1; break; |
174 | } |
175 | } |
176 | |
177 | return _exclusions.begin()+l; |
178 | } |
179 | |
180 | |
181 | float Zones::closest(float origin, float & cost) const |
182 | { |
183 | float best_c = std::numeric_limits<float>::max(), |
184 | best_x = 0; |
185 | |
186 | const const_iterator start = find_exclusion_under(origin); |
187 | |
188 | // Forward scan looking for lowest cost |
189 | for (const_iterator i = start, ie = _exclusions.end(); i != ie; ++i) |
190 | if (i->track_cost(best_c, best_x, origin)) break; |
191 | |
192 | // Backward scan looking for lowest cost |
193 | // We start from the exclusion to the immediate left of start since we've |
194 | // already tested start with the right most scan above. |
195 | for (const_iterator i = start-1, ie = _exclusions.begin()-1; i != ie; --i) |
196 | if (i->track_cost(best_c, best_x, origin)) break; |
197 | |
198 | cost = (best_c == std::numeric_limits<float>::max() ? -1 : best_c); |
199 | return best_x; |
200 | } |
201 | |
202 | |
203 | // Cost and test position functions |
204 | |
205 | bool Zones::Exclusion::track_cost(float & best_cost, float & best_pos, float origin) const { |
206 | const float p = test_position(origin), |
207 | localc = cost(p - origin); |
208 | if (open && localc > best_cost) return true; |
209 | |
210 | if (localc < best_cost) |
211 | { |
212 | best_cost = localc; |
213 | best_pos = p; |
214 | } |
215 | return false; |
216 | } |
217 | |
218 | inline |
219 | float Zones::Exclusion::cost(float p) const { |
220 | return (sm * p - 2 * smx) * p + c; |
221 | } |
222 | |
223 | |
224 | float Zones::Exclusion::test_position(float origin) const { |
225 | if (sm < 0) |
226 | { |
227 | // sigh, test both ends and perhaps the middle too! |
228 | float res = x; |
229 | float cl = cost(x); |
230 | if (x < origin && xm > origin) |
231 | { |
232 | float co = cost(origin); |
233 | if (co < cl) |
234 | { |
235 | cl = co; |
236 | res = origin; |
237 | } |
238 | } |
239 | float cr = cost(xm); |
240 | return cl > cr ? xm : res; |
241 | } |
242 | else |
243 | { |
244 | float zerox = smx / sm + origin; |
245 | if (zerox < x) return x; |
246 | else if (zerox > xm) return xm; |
247 | else return zerox; |
248 | } |
249 | } |
250 | |
251 | |
252 | #if !defined GRAPHITE2_NTRACING |
253 | |
254 | void Zones::jsonDbgOut(Segment *seg) const { |
255 | |
256 | if (_dbg) |
257 | { |
258 | for (Zones::idebugs s = dbgs_begin(), e = dbgs_end(); s != e; ++s) |
259 | { |
260 | *_dbg << json::flat << json::array |
261 | << objectid(dslot(seg, (Slot *)(s->_env[0]))) |
262 | << reinterpret_cast<ptrdiff_t>(s->_env[1]); |
263 | if (s->_isdel) |
264 | *_dbg << "remove" << Position(s->_excl.x, s->_excl.xm); |
265 | else |
266 | *_dbg << "exclude" << json::flat << json::array |
267 | << s->_excl.x << s->_excl.xm |
268 | << s->_excl.sm << s->_excl.smx << s->_excl.c |
269 | << json::close; |
270 | *_dbg << json::close; |
271 | } |
272 | } |
273 | } |
274 | |
275 | #endif |
276 | |