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 <limits> |
6 | #include <cmath> |
7 | #include <string> |
8 | #include <functional> |
9 | #include "inc/Collider.h" |
10 | #include "inc/Segment.h" |
11 | #include "inc/Slot.h" |
12 | #include "inc/GlyphCache.h" |
13 | #include "inc/Sparse.h" |
14 | |
15 | #define ISQRT2 0.707106781f |
16 | |
17 | // Possible rounding error for subbox boundaries: 0.016 = 1/64 = 1/256 * 4 |
18 | // (values in font range from 0..256) |
19 | // #define SUBBOX_RND_ERR 0.016 |
20 | |
21 | using namespace graphite2; |
22 | |
23 | //// SHIFT-COLLIDER //// |
24 | |
25 | // Initialize the Collider to hold the basic movement limits for the |
26 | // target slot, the one we are focusing on fixing. |
27 | bool ShiftCollider::initSlot(Segment *seg, Slot *aSlot, const Rect &limit, float margin, float marginWeight, |
28 | const Position &currShift, const Position &currOffset, int dir, GR_MAYBE_UNUSED json * const dbgout) |
29 | { |
30 | int i; |
31 | float mx, mn; |
32 | float a, shift; |
33 | const GlyphCache &gc = seg->getFace()->glyphs(); |
34 | unsigned short gid = aSlot->gid(); |
35 | if (!gc.check(gid)) |
36 | return false; |
37 | const BBox &bb = gc.getBoundingBBox(gid); |
38 | const SlantBox &sb = gc.getBoundingSlantBox(gid); |
39 | //float sx = aSlot->origin().x + currShift.x; |
40 | //float sy = aSlot->origin().y + currShift.y; |
41 | if (currOffset.x != 0.f || currOffset.y != 0.f) |
42 | _limit = Rect(limit.bl - currOffset, limit.tr - currOffset); |
43 | else |
44 | _limit = limit; |
45 | // For a ShiftCollider, these indices indicate which vector we are moving by: |
46 | // each _ranges represents absolute space with respect to the origin of the slot. Thus take into account true origins but subtract the vmin for the slot |
47 | for (i = 0; i < 4; ++i) |
48 | { |
49 | switch (i) { |
50 | case 0 : // x direction |
51 | mn = _limit.bl.x + currOffset.x; |
52 | mx = _limit.tr.x + currOffset.x; |
53 | _len[i] = bb.xa - bb.xi; |
54 | a = currOffset.y + currShift.y; |
55 | _ranges[i].initialise<XY>(mn, mx, margin, marginWeight, a); |
56 | break; |
57 | case 1 : // y direction |
58 | mn = _limit.bl.y + currOffset.y; |
59 | mx = _limit.tr.y + currOffset.y; |
60 | _len[i] = bb.ya - bb.yi; |
61 | a = currOffset.x + currShift.x; |
62 | _ranges[i].initialise<XY>(mn, mx, margin, marginWeight, a); |
63 | break; |
64 | case 2 : // sum (negatively sloped diagonal boundaries) |
65 | // pick closest x,y limit boundaries in s direction |
66 | shift = currOffset.x + currOffset.y + currShift.x + currShift.y; |
67 | mn = -2 * min(currShift.x - _limit.bl.x, currShift.y - _limit.bl.y) + shift; |
68 | mx = 2 * min(_limit.tr.x - currShift.x, _limit.tr.y - currShift.y) + shift; |
69 | _len[i] = sb.sa - sb.si; |
70 | a = currOffset.x - currOffset.y + currShift.x - currShift.y; |
71 | _ranges[i].initialise<SD>(mn, mx, margin / ISQRT2, marginWeight, a); |
72 | break; |
73 | case 3 : // diff (positively sloped diagonal boundaries) |
74 | // pick closest x,y limit boundaries in d direction |
75 | shift = currOffset.x - currOffset.y + currShift.x - currShift.y; |
76 | mn = -2 * min(currShift.x - _limit.bl.x, _limit.tr.y - currShift.y) + shift; |
77 | mx = 2 * min(_limit.tr.x - currShift.x, currShift.y - _limit.bl.y) + shift; |
78 | _len[i] = sb.da - sb.di; |
79 | a = currOffset.x + currOffset.y + currShift.x + currShift.y; |
80 | _ranges[i].initialise<SD>(mn, mx, margin / ISQRT2, marginWeight, a); |
81 | break; |
82 | } |
83 | } |
84 | |
85 | _target = aSlot; |
86 | if ((dir & 1) == 0) |
87 | { |
88 | // For LTR, switch and negate x limits. |
89 | _limit.bl.x = -1 * limit.tr.x; |
90 | //_limit.tr.x = -1 * limit.bl.x; |
91 | } |
92 | _currOffset = currOffset; |
93 | _currShift = currShift; |
94 | _origin = aSlot->origin() - currOffset; // the original anchor position of the glyph |
95 | |
96 | _margin = margin; |
97 | _marginWt = marginWeight; |
98 | |
99 | SlotCollision *c = seg->collisionInfo(aSlot); |
100 | _seqClass = c->seqClass(); |
101 | _seqProxClass = c->seqProxClass(); |
102 | _seqOrder = c->seqOrder(); |
103 | return true; |
104 | } |
105 | |
106 | template <class O> |
107 | float sdm(float vi, float va, float mx, float my, O op) |
108 | { |
109 | float res = 2 * mx - vi; |
110 | if (op(res, vi + 2 * my)) |
111 | { |
112 | res = va + 2 * my; |
113 | if (op(res, 2 * mx - va)) |
114 | res = mx + my; |
115 | } |
116 | return res; |
117 | } |
118 | |
119 | // Mark an area with a cost that can vary along the x or y axis. The region is expressed in terms of the centre of the target glyph in each axis |
120 | void ShiftCollider::addBox_slope(bool isx, const Rect &box, const BBox &bb, const SlantBox &sb, const Position &org, float weight, float m, bool minright, int axis) |
121 | { |
122 | float a, c; |
123 | switch (axis) { |
124 | case 0 : |
125 | if (box.bl.y < org.y + bb.ya && box.tr.y > org.y + bb.yi && box.width() > 0) |
126 | { |
127 | a = org.y + 0.5f * (bb.yi + bb.ya); |
128 | c = 0.5f * (bb.xi + bb.xa); |
129 | if (isx) |
130 | _ranges[axis].weighted<XY>(box.bl.x - c, box.tr.x - c, weight, a, m, |
131 | (minright ? box.tr.x : box.bl.x) - c, a, 0, false); |
132 | else |
133 | _ranges[axis].weighted<XY>(box.bl.x - c, box.tr.x - c, weight, a, 0, 0, org.y, |
134 | m * (a * a + sqr((minright ? box.tr.y : box.bl.y) - 0.5f * (bb.yi + bb.ya))), false); |
135 | } |
136 | break; |
137 | case 1 : |
138 | if (box.bl.x < org.x + bb.xa && box.tr.x > org.x + bb.xi && box.height() > 0) |
139 | { |
140 | a = org.x + 0.5f * (bb.xi + bb.xa); |
141 | c = 0.5f * (bb.yi + bb.ya); |
142 | if (isx) |
143 | _ranges[axis].weighted<XY>(box.bl.y - c, box.tr.y - c, weight, a, 0, 0, org.x, |
144 | m * (a * a + sqr((minright ? box.tr.x : box.bl.x) - 0.5f * (bb.xi + bb.xa))), false); |
145 | else |
146 | _ranges[axis].weighted<XY>(box.bl.y - c, box.tr.y - c, weight, a, m, |
147 | (minright ? box.tr.y : box.bl.y) - c, a, 0, false); |
148 | } |
149 | break; |
150 | case 2 : |
151 | if (box.bl.x - box.tr.y < org.x - org.y + sb.da && box.tr.x - box.bl.y > org.x - org.y + sb.di) |
152 | { |
153 | float d = org.x - org.y + 0.5f * (sb.di + sb.da); |
154 | c = 0.5f * (sb.si + sb.sa); |
155 | float smax = min(2 * box.tr.x - d, 2 * box.tr.y + d); |
156 | float smin = max(2 * box.bl.x - d, 2 * box.bl.y + d); |
157 | if (smin > smax) return; |
158 | float si; |
159 | a = d; |
160 | if (isx) |
161 | si = 2 * (minright ? box.tr.x : box.bl.x) - a; |
162 | else |
163 | si = 2 * (minright ? box.tr.y : box.bl.y) + a; |
164 | _ranges[axis].weighted<SD>(smin - c, smax - c, weight / 2, a, m / 2, si, 0, 0, isx); |
165 | } |
166 | break; |
167 | case 3 : |
168 | if (box.bl.x + box.bl.y < org.x + org.y + sb.sa && box.tr.x + box.tr.y > org.x + org.y + sb.si) |
169 | { |
170 | float s = org.x + org.y + 0.5f * (sb.si + sb.sa); |
171 | c = 0.5f * (sb.di + sb.da); |
172 | float dmax = min(2 * box.tr.x - s, s - 2 * box.bl.y); |
173 | float dmin = max(2 * box.bl.x - s, s - 2 * box.tr.y); |
174 | if (dmin > dmax) return; |
175 | float di; |
176 | a = s; |
177 | if (isx) |
178 | di = 2 * (minright ? box.tr.x : box.bl.x) - a; |
179 | else |
180 | di = 2 * (minright ? box.tr.y : box.bl.y) + a; |
181 | _ranges[axis].weighted<SD>(dmin - c, dmax - c, weight / 2, a, m / 2, di, 0, 0, !isx); |
182 | } |
183 | break; |
184 | default : |
185 | break; |
186 | } |
187 | return; |
188 | } |
189 | |
190 | // Mark an area with an absolute cost, making it completely inaccessible. |
191 | inline void ShiftCollider::removeBox(const Rect &box, const BBox &bb, const SlantBox &sb, const Position &org, int axis) |
192 | { |
193 | float c; |
194 | switch (axis) { |
195 | case 0 : |
196 | if (box.bl.y < org.y + bb.ya && box.tr.y > org.y + bb.yi && box.width() > 0) |
197 | { |
198 | c = 0.5f * (bb.xi + bb.xa); |
199 | _ranges[axis].exclude(box.bl.x - c, box.tr.x - c); |
200 | } |
201 | break; |
202 | case 1 : |
203 | if (box.bl.x < org.x + bb.xa && box.tr.x > org.x + bb.xi && box.height() > 0) |
204 | { |
205 | c = 0.5f * (bb.yi + bb.ya); |
206 | _ranges[axis].exclude(box.bl.y - c, box.tr.y - c); |
207 | } |
208 | break; |
209 | case 2 : |
210 | if (box.bl.x - box.tr.y < org.x - org.y + sb.da && box.tr.x - box.bl.y > org.x - org.y + sb.di |
211 | && box.width() > 0 && box.height() > 0) |
212 | { |
213 | float di = org.x - org.y + sb.di; |
214 | float da = org.x - org.y + sb.da; |
215 | float smax = sdm(di, da, box.tr.x, box.tr.y, std::greater<float>()); |
216 | float smin = sdm(da, di, box.bl.x, box.bl.y, std::less<float>()); |
217 | c = 0.5f * (sb.si + sb.sa); |
218 | _ranges[axis].exclude(smin - c, smax - c); |
219 | } |
220 | break; |
221 | case 3 : |
222 | if (box.bl.x + box.bl.y < org.x + org.y + sb.sa && box.tr.x + box.tr.y > org.x + org.y + sb.si |
223 | && box.width() > 0 && box.height() > 0) |
224 | { |
225 | float si = org.x + org.y + sb.si; |
226 | float sa = org.x + org.y + sb.sa; |
227 | float dmax = sdm(si, sa, box.tr.x, -box.bl.y, std::greater<float>()); |
228 | float dmin = sdm(sa, si, box.bl.x, -box.tr.y, std::less<float>()); |
229 | c = 0.5f * (sb.di + sb.da); |
230 | _ranges[axis].exclude(dmin - c, dmax - c); |
231 | } |
232 | break; |
233 | default : |
234 | break; |
235 | } |
236 | return; |
237 | } |
238 | |
239 | // Adjust the movement limits for the target to avoid having it collide |
240 | // with the given neighbor slot. Also determine if there is in fact a collision |
241 | // between the target and the given slot. |
242 | bool ShiftCollider::mergeSlot(Segment *seg, Slot *slot, const SlotCollision *cslot, const Position &currShift, |
243 | bool isAfter, // slot is logically after _target |
244 | bool sameCluster, bool &hasCol, bool isExclusion, |
245 | GR_MAYBE_UNUSED json * const dbgout ) |
246 | { |
247 | bool isCol = false; |
248 | const float sx = slot->origin().x - _origin.x + currShift.x; |
249 | const float sy = slot->origin().y - _origin.y + currShift.y; |
250 | const float sd = sx - sy; |
251 | const float ss = sx + sy; |
252 | float vmin, vmax; |
253 | float omin, omax, otmin, otmax; |
254 | float cmin, cmax; // target limits |
255 | float torg; |
256 | const GlyphCache &gc = seg->getFace()->glyphs(); |
257 | const unsigned short gid = slot->gid(); |
258 | if (!gc.check(gid)) |
259 | return false; |
260 | const BBox &bb = gc.getBoundingBBox(gid); |
261 | |
262 | // SlotCollision * cslot = seg->collisionInfo(slot); |
263 | int orderFlags = 0; |
264 | bool sameClass = _seqProxClass == 0 && cslot->seqClass() == _seqClass; |
265 | if (sameCluster && _seqClass |
266 | && (sameClass || (_seqProxClass != 0 && cslot->seqClass() == _seqProxClass))) |
267 | // Force the target glyph to be in the specified direction from the slot we're testing. |
268 | orderFlags = _seqOrder; |
269 | |
270 | // short circuit if only interested in direct collision and we are out of range |
271 | if (orderFlags || (sx + bb.xa + _margin >= _limit.bl.x && sx + bb.xi - _margin <= _limit.tr.x) |
272 | || (sy + bb.ya + _margin >= _limit.bl.y && sy + bb.yi - _margin <= _limit.tr.y)) |
273 | |
274 | { |
275 | const float tx = _currOffset.x + _currShift.x; |
276 | const float ty = _currOffset.y + _currShift.y; |
277 | const float td = tx - ty; |
278 | const float ts = tx + ty; |
279 | const SlantBox &sb = gc.getBoundingSlantBox(gid); |
280 | const unsigned short tgid = _target->gid(); |
281 | const BBox &tbb = gc.getBoundingBBox(tgid); |
282 | const SlantBox &tsb = gc.getBoundingSlantBox(tgid); |
283 | float seq_above_wt = cslot->seqAboveWt(); |
284 | float seq_below_wt = cslot->seqBelowWt(); |
285 | float seq_valign_wt = cslot->seqValignWt(); |
286 | float lmargin; |
287 | // if isAfter, invert orderFlags for diagonal orders. |
288 | if (isAfter) |
289 | { |
290 | // invert appropriate bits |
291 | orderFlags ^= (sameClass ? 0x3F : 0x3); |
292 | // consider 2 bits at a time, non overlapping. If both bits set, clear them |
293 | orderFlags = orderFlags ^ ((((orderFlags >> 1) & orderFlags) & 0x15) * 3); |
294 | } |
295 | |
296 | #if !defined GRAPHITE2_NTRACING |
297 | if (dbgout) |
298 | dbgout->setenv(0, slot); |
299 | #endif |
300 | |
301 | // Process main bounding octabox. |
302 | for (int i = 0; i < 4; ++i) |
303 | { |
304 | switch (i) { |
305 | case 0 : // x direction |
306 | vmin = max(max(bb.xi - tbb.xa + sx, sb.di - tsb.da + ty + sd), sb.si - tsb.sa - ty + ss); |
307 | vmax = min(min(bb.xa - tbb.xi + sx, sb.da - tsb.di + ty + sd), sb.sa - tsb.si - ty + ss); |
308 | otmin = tbb.yi + ty; |
309 | otmax = tbb.ya + ty; |
310 | omin = bb.yi + sy; |
311 | omax = bb.ya + sy; |
312 | torg = _currOffset.x; |
313 | cmin = _limit.bl.x + torg; |
314 | cmax = _limit.tr.x - tbb.xi + tbb.xa + torg; |
315 | lmargin = _margin; |
316 | break; |
317 | case 1 : // y direction |
318 | vmin = max(max(bb.yi - tbb.ya + sy, tsb.di - sb.da + tx - sd), sb.si - tsb.sa - tx + ss); |
319 | vmax = min(min(bb.ya - tbb.yi + sy, tsb.da - sb.di + tx - sd), sb.sa - tsb.si - tx + ss); |
320 | otmin = tbb.xi + tx; |
321 | otmax = tbb.xa + tx; |
322 | omin = bb.xi + sx; |
323 | omax = bb.xa + sx; |
324 | torg = _currOffset.y; |
325 | cmin = _limit.bl.y + torg; |
326 | cmax = _limit.tr.y - tbb.yi + tbb.ya + torg; |
327 | lmargin = _margin; |
328 | break; |
329 | case 2 : // sum - moving along the positively-sloped vector, so the boundaries are the |
330 | // negatively-sloped boundaries. |
331 | vmin = max(max(sb.si - tsb.sa + ss, 2 * (bb.yi - tbb.ya + sy) + td), 2 * (bb.xi - tbb.xa + sx) - td); |
332 | vmax = min(min(sb.sa - tsb.si + ss, 2 * (bb.ya - tbb.yi + sy) + td), 2 * (bb.xa - tbb.xi + sx) - td); |
333 | otmin = tsb.di + td; |
334 | otmax = tsb.da + td; |
335 | omin = sb.di + sd; |
336 | omax = sb.da + sd; |
337 | torg = _currOffset.x + _currOffset.y; |
338 | cmin = _limit.bl.x + _limit.bl.y + torg; |
339 | cmax = _limit.tr.x + _limit.tr.y - tsb.si + tsb.sa + torg; |
340 | lmargin = _margin / ISQRT2; |
341 | break; |
342 | case 3 : // diff - moving along the negatively-sloped vector, so the boundaries are the |
343 | // positively-sloped boundaries. |
344 | vmin = max(max(sb.di - tsb.da + sd, 2 * (bb.xi - tbb.xa + sx) - ts), -2 * (bb.ya - tbb.yi + sy) + ts); |
345 | vmax = min(min(sb.da - tsb.di + sd, 2 * (bb.xa - tbb.xi + sx) - ts), -2 * (bb.yi - tbb.ya + sy) + ts); |
346 | otmin = tsb.si + ts; |
347 | otmax = tsb.sa + ts; |
348 | omin = sb.si + ss; |
349 | omax = sb.sa + ss; |
350 | torg = _currOffset.x - _currOffset.y; |
351 | cmin = _limit.bl.x - _limit.tr.y + torg; |
352 | cmax = _limit.tr.x - _limit.bl.y - tsb.di + tsb.da + torg; |
353 | lmargin = _margin / ISQRT2; |
354 | break; |
355 | default : |
356 | continue; |
357 | } |
358 | |
359 | #if !defined GRAPHITE2_NTRACING |
360 | if (dbgout) |
361 | dbgout->setenv(1, reinterpret_cast<void *>(-1)); |
362 | #define DBGTAG(x) if (dbgout) dbgout->setenv(1, reinterpret_cast<void *>(-x)); |
363 | #else |
364 | #define DBGTAG(x) |
365 | #endif |
366 | |
367 | if (orderFlags) |
368 | { |
369 | Position org(tx, ty); |
370 | float xminf = _limit.bl.x + _currOffset.x + tbb.xi; |
371 | float xpinf = _limit.tr.x + _currOffset.x + tbb.xa; |
372 | float ypinf = _limit.tr.y + _currOffset.y + tbb.ya; |
373 | float yminf = _limit.bl.y + _currOffset.y + tbb.yi; |
374 | switch (orderFlags) { |
375 | case SlotCollision::SEQ_ORDER_RIGHTUP : |
376 | { |
377 | float r1Xedge = cslot->seqAboveXoff() + 0.5f * (bb.xi + bb.xa) + sx; |
378 | float r3Xedge = cslot->seqBelowXlim() + bb.xa + sx + 0.5f * (tbb.xa - tbb.xi); |
379 | float r2Yedge = 0.5f * (bb.yi + bb.ya) + sy; |
380 | |
381 | // DBGTAG(1x) means the regions are up and right |
382 | // region 1 |
383 | DBGTAG(11) |
384 | addBox_slope(true, Rect(Position(xminf, r2Yedge), Position(r1Xedge, ypinf)), |
385 | tbb, tsb, org, 0, seq_above_wt, true, i); |
386 | // region 2 |
387 | DBGTAG(12) |
388 | removeBox(Rect(Position(xminf, yminf), Position(r3Xedge, r2Yedge)), tbb, tsb, org, i); |
389 | // region 3, which end is zero is irrelevant since m weight is 0 |
390 | DBGTAG(13) |
391 | addBox_slope(true, Rect(Position(r3Xedge, yminf), Position(xpinf, r2Yedge - cslot->seqValignHt())), |
392 | tbb, tsb, org, seq_below_wt, 0, true, i); |
393 | // region 4 |
394 | DBGTAG(14) |
395 | addBox_slope(false, Rect(Position(sx + bb.xi, r2Yedge), Position(xpinf, r2Yedge + cslot->seqValignHt())), |
396 | tbb, tsb, org, 0, seq_valign_wt, true, i); |
397 | // region 5 |
398 | DBGTAG(15) |
399 | addBox_slope(false, Rect(Position(sx + bb.xi, r2Yedge - cslot->seqValignHt()), Position(xpinf, r2Yedge)), |
400 | tbb, tsb, org, seq_below_wt, seq_valign_wt, false, i); |
401 | break; |
402 | } |
403 | case SlotCollision::SEQ_ORDER_LEFTDOWN : |
404 | { |
405 | float r1Xedge = 0.5f * (bb.xi + bb.xa) + cslot->seqAboveXoff() + sx; |
406 | float r3Xedge = bb.xi - cslot->seqBelowXlim() + sx - 0.5f * (tbb.xa - tbb.xi); |
407 | float r2Yedge = 0.5f * (bb.yi + bb.ya) + sy; |
408 | // DBGTAG(2x) means the regions are up and right |
409 | // region 1 |
410 | DBGTAG(21) |
411 | addBox_slope(true, Rect(Position(r1Xedge, yminf), Position(xpinf, r2Yedge)), |
412 | tbb, tsb, org, 0, seq_above_wt, false, i); |
413 | // region 2 |
414 | DBGTAG(22) |
415 | removeBox(Rect(Position(r3Xedge, r2Yedge), Position(xpinf, ypinf)), tbb, tsb, org, i); |
416 | // region 3 |
417 | DBGTAG(23) |
418 | addBox_slope(true, Rect(Position(xminf, r2Yedge - cslot->seqValignHt()), Position(r3Xedge, ypinf)), |
419 | tbb, tsb, org, seq_below_wt, 0, false, i); |
420 | // region 4 |
421 | DBGTAG(24) |
422 | addBox_slope(false, Rect(Position(xminf, r2Yedge), Position(sx + bb.xa, r2Yedge + cslot->seqValignHt())), |
423 | tbb, tsb, org, 0, seq_valign_wt, true, i); |
424 | // region 5 |
425 | DBGTAG(25) |
426 | addBox_slope(false, Rect(Position(xminf, r2Yedge - cslot->seqValignHt()), |
427 | Position(sx + bb.xa, r2Yedge)), tbb, tsb, org, seq_below_wt, seq_valign_wt, false, i); |
428 | break; |
429 | } |
430 | case SlotCollision::SEQ_ORDER_NOABOVE : // enforce neighboring glyph being above |
431 | DBGTAG(31); |
432 | removeBox(Rect(Position(bb.xi - tbb.xa + sx, sy + bb.ya), |
433 | Position(bb.xa - tbb.xi + sx, ypinf)), tbb, tsb, org, i); |
434 | break; |
435 | case SlotCollision::SEQ_ORDER_NOBELOW : // enforce neighboring glyph being below |
436 | DBGTAG(32); |
437 | removeBox(Rect(Position(bb.xi - tbb.xa + sx, yminf), |
438 | Position(bb.xa - tbb.xi + sx, sy + bb.yi)), tbb, tsb, org, i); |
439 | break; |
440 | case SlotCollision::SEQ_ORDER_NOLEFT : // enforce neighboring glyph being to the left |
441 | DBGTAG(33) |
442 | removeBox(Rect(Position(xminf, bb.yi - tbb.ya + sy), |
443 | Position(bb.xi - tbb.xa + sx, bb.ya - tbb.yi + sy)), tbb, tsb, org, i); |
444 | break; |
445 | case SlotCollision::SEQ_ORDER_NORIGHT : // enforce neighboring glyph being to the right |
446 | DBGTAG(34) |
447 | removeBox(Rect(Position(bb.xa - tbb.xi + sx, bb.yi - tbb.ya + sy), |
448 | Position(xpinf, bb.ya - tbb.yi + sy)), tbb, tsb, org, i); |
449 | break; |
450 | default : |
451 | break; |
452 | } |
453 | } |
454 | |
455 | if (vmax < cmin - lmargin || vmin > cmax + lmargin || omax < otmin - lmargin || omin > otmax + lmargin) |
456 | continue; |
457 | |
458 | // Process sub-boxes that are defined for this glyph. |
459 | // We only need to do this if there was in fact a collision with the main octabox. |
460 | uint8 numsub = gc.numSubBounds(gid); |
461 | if (numsub > 0) |
462 | { |
463 | bool anyhits = false; |
464 | for (int j = 0; j < numsub; ++j) |
465 | { |
466 | const BBox &sbb = gc.getSubBoundingBBox(gid, j); |
467 | const SlantBox &ssb = gc.getSubBoundingSlantBox(gid, j); |
468 | switch (i) { |
469 | case 0 : // x |
470 | vmin = max(max(sbb.xi-tbb.xa+sx, ssb.di-tsb.da+sd+ty), ssb.si-tsb.sa+ss-ty); |
471 | vmax = min(min(sbb.xa-tbb.xi+sx, ssb.da-tsb.di+sd+ty), ssb.sa-tsb.si+ss-ty); |
472 | omin = sbb.yi + sy; |
473 | omax = sbb.ya + sy; |
474 | break; |
475 | case 1 : // y |
476 | vmin = max(max(sbb.yi-tbb.ya+sy, tsb.di-ssb.da-sd+tx), ssb.si-tsb.sa+ss-tx); |
477 | vmax = min(min(sbb.ya-tbb.yi+sy, tsb.da-ssb.di-sd+tx), ssb.sa-tsb.si+ss-tx); |
478 | omin = sbb.xi + sx; |
479 | omax = sbb.xa + sx; |
480 | break; |
481 | case 2 : // sum |
482 | vmin = max(max(ssb.si-tsb.sa+ss, 2*(sbb.yi-tbb.ya+sy)+td), 2*(sbb.xi-tbb.xa+sx)-td); |
483 | vmax = min(min(ssb.sa-tsb.si+ss, 2*(sbb.ya-tbb.yi+sy)+td), 2*(sbb.xa-tbb.xi+sx)-td); |
484 | omin = ssb.di + sd; |
485 | omax = ssb.da + sd; |
486 | break; |
487 | case 3 : // diff |
488 | vmin = max(max(ssb.di-tsb.da+sd, 2*(sbb.xi-tbb.xa+sx)-ts), -2*(sbb.ya-tbb.yi+sy)+ts); |
489 | vmax = min(min(ssb.da-tsb.di+sd, 2*(sbb.xa-tbb.xi+sx)-ts), -2*(sbb.yi-tbb.ya+sy)+ts); |
490 | omin = ssb.si + ss; |
491 | omax = ssb.sa + ss; |
492 | break; |
493 | } |
494 | if (vmax < cmin - lmargin || vmin > cmax + lmargin || omax < otmin - lmargin || omin > otmax + lmargin) |
495 | continue; |
496 | |
497 | #if !defined GRAPHITE2_NTRACING |
498 | if (dbgout) |
499 | dbgout->setenv(1, reinterpret_cast<void *>(j)); |
500 | #endif |
501 | if (omin > otmax) |
502 | _ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0, |
503 | sqr(lmargin - omin + otmax) * _marginWt, false); |
504 | else if (omax < otmin) |
505 | _ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0, |
506 | sqr(lmargin - otmin + omax) * _marginWt, false); |
507 | else |
508 | _ranges[i].exclude_with_margins(vmin, vmax, i); |
509 | anyhits = true; |
510 | } |
511 | if (anyhits) |
512 | isCol = true; |
513 | } |
514 | else // no sub-boxes |
515 | { |
516 | #if !defined GRAPHITE2_NTRACING |
517 | if (dbgout) |
518 | dbgout->setenv(1, reinterpret_cast<void *>(-1)); |
519 | #endif |
520 | isCol = true; |
521 | if (omin > otmax) |
522 | _ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0, |
523 | sqr(lmargin - omin + otmax) * _marginWt, false); |
524 | else if (omax < otmin) |
525 | _ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0, |
526 | sqr(lmargin - otmin + omax) * _marginWt, false); |
527 | else |
528 | _ranges[i].exclude_with_margins(vmin, vmax, i); |
529 | |
530 | } |
531 | } |
532 | } |
533 | bool res = true; |
534 | if (cslot->exclGlyph() > 0 && gc.check(cslot->exclGlyph()) && !isExclusion) |
535 | { |
536 | // Set up the bogus slot representing the exclusion glyph. |
537 | Slot *exclSlot = seg->newSlot(); |
538 | if (!exclSlot) |
539 | return res; |
540 | exclSlot->setGlyph(seg, cslot->exclGlyph()); |
541 | Position exclOrigin(slot->origin() + cslot->exclOffset()); |
542 | exclSlot->origin(exclOrigin); |
543 | SlotCollision exclInfo(seg, exclSlot); |
544 | res &= mergeSlot(seg, exclSlot, &exclInfo, currShift, isAfter, sameCluster, isCol, true, dbgout ); |
545 | seg->freeSlot(exclSlot); |
546 | } |
547 | hasCol |= isCol; |
548 | return res; |
549 | |
550 | } // end of ShiftCollider::mergeSlot |
551 | |
552 | |
553 | // Figure out where to move the target glyph to, and return the amount to shift by. |
554 | Position ShiftCollider::resolve(GR_MAYBE_UNUSED Segment *seg, bool &isCol, GR_MAYBE_UNUSED json * const dbgout) |
555 | { |
556 | float tbase; |
557 | float totalCost = (float)(std::numeric_limits<float>::max() / 2); |
558 | Position resultPos = Position(0, 0); |
559 | #if !defined GRAPHITE2_NTRACING |
560 | int bestAxis = -1; |
561 | if (dbgout) |
562 | { |
563 | outputJsonDbgStartSlot(dbgout, seg); |
564 | *dbgout << "vectors" << json::array; |
565 | } |
566 | #endif |
567 | isCol = true; |
568 | for (int i = 0; i < 4; ++i) |
569 | { |
570 | float bestCost = -1; |
571 | float bestPos; |
572 | // Calculate the margin depending on whether we are moving diagonally or not: |
573 | switch (i) { |
574 | case 0 : // x direction |
575 | tbase = _currOffset.x; |
576 | break; |
577 | case 1 : // y direction |
578 | tbase = _currOffset.y; |
579 | break; |
580 | case 2 : // sum (negatively-sloped diagonals) |
581 | tbase = _currOffset.x + _currOffset.y; |
582 | break; |
583 | case 3 : // diff (positively-sloped diagonals) |
584 | tbase = _currOffset.x - _currOffset.y; |
585 | break; |
586 | } |
587 | Position testp; |
588 | bestPos = _ranges[i].closest(0, bestCost) - tbase; // Get the best relative position |
589 | #if !defined GRAPHITE2_NTRACING |
590 | if (dbgout) |
591 | outputJsonDbgOneVector(dbgout, seg, i, tbase, bestCost, bestPos) ; |
592 | #endif |
593 | if (bestCost >= 0.0f) |
594 | { |
595 | isCol = false; |
596 | switch (i) { |
597 | case 0 : testp = Position(bestPos, _currShift.y); break; |
598 | case 1 : testp = Position(_currShift.x, bestPos); break; |
599 | case 2 : testp = Position(0.5f * (_currShift.x - _currShift.y + bestPos), 0.5f * (_currShift.y - _currShift.x + bestPos)); break; |
600 | case 3 : testp = Position(0.5f * (_currShift.x + _currShift.y + bestPos), 0.5f * (_currShift.x + _currShift.y - bestPos)); break; |
601 | } |
602 | if (bestCost < totalCost - 0.01f) |
603 | { |
604 | totalCost = bestCost; |
605 | resultPos = testp; |
606 | #if !defined GRAPHITE2_NTRACING |
607 | bestAxis = i; |
608 | #endif |
609 | } |
610 | } |
611 | } // end of loop over 4 directions |
612 | |
613 | #if !defined GRAPHITE2_NTRACING |
614 | if (dbgout) |
615 | outputJsonDbgEndSlot(dbgout, resultPos, bestAxis, isCol); |
616 | #endif |
617 | |
618 | return resultPos; |
619 | |
620 | } // end of ShiftCollider::resolve |
621 | |
622 | |
623 | #if !defined GRAPHITE2_NTRACING |
624 | |
625 | void ShiftCollider::outputJsonDbg(json * const dbgout, Segment *seg, int axis) |
626 | { |
627 | int axisMax = axis; |
628 | if (axis < 0) // output all axes |
629 | { |
630 | *dbgout << "gid" << _target->gid() |
631 | << "limit" << _limit |
632 | << "target" << json::object |
633 | << "origin" << _target->origin() |
634 | << "margin" << _margin |
635 | << "bbox" << seg->theGlyphBBoxTemporary(_target->gid()) |
636 | << "slantbox" << seg->getFace()->glyphs().slant(_target->gid()) |
637 | << json::close; // target object |
638 | *dbgout << "ranges" << json::array; |
639 | axis = 0; |
640 | axisMax = 3; |
641 | } |
642 | for (int iAxis = axis; iAxis <= axisMax; ++iAxis) |
643 | { |
644 | *dbgout << json::flat << json::array << _ranges[iAxis].position(); |
645 | for (Zones::const_iterator s = _ranges[iAxis].begin(), e = _ranges[iAxis].end(); s != e; ++s) |
646 | *dbgout << json::flat << json::array |
647 | << Position(s->x, s->xm) << s->sm << s->smx << s->c |
648 | << json::close; |
649 | *dbgout << json::close; |
650 | } |
651 | if (axis < axisMax) // looped through the _ranges array for all axes |
652 | *dbgout << json::close; // ranges array |
653 | } |
654 | |
655 | void ShiftCollider::outputJsonDbgStartSlot(json * const dbgout, Segment *seg) |
656 | { |
657 | *dbgout << json::object // slot - not closed till the end of the caller method |
658 | << "slot" << objectid(dslot(seg, _target)) |
659 | << "gid" << _target->gid() |
660 | << "limit" << _limit |
661 | << "target" << json::object |
662 | << "origin" << _origin |
663 | << "currShift" << _currShift |
664 | << "currOffset" << seg->collisionInfo(_target)->offset() |
665 | << "bbox" << seg->theGlyphBBoxTemporary(_target->gid()) |
666 | << "slantBox" << seg->getFace()->glyphs().slant(_target->gid()) |
667 | << "fix" << "shift" ; |
668 | *dbgout << json::close; // target object |
669 | } |
670 | |
671 | void ShiftCollider::outputJsonDbgEndSlot(GR_MAYBE_UNUSED json * const dbgout, |
672 | Position resultPos, int bestAxis, bool isCol) |
673 | { |
674 | *dbgout << json::close // vectors array |
675 | << "result" << resultPos |
676 | //<< "scraping" << _scraping[bestAxis] |
677 | << "bestAxis" << bestAxis |
678 | << "stillBad" << isCol |
679 | << json::close; // slot object |
680 | } |
681 | |
682 | void ShiftCollider::outputJsonDbgOneVector(json * const dbgout, Segment *seg, int axis, |
683 | float tleft, float bestCost, float bestVal) |
684 | { |
685 | const char * label; |
686 | switch (axis) |
687 | { |
688 | case 0: label = "x" ; break; |
689 | case 1: label = "y" ; break; |
690 | case 2: label = "sum (NE-SW)" ; break; |
691 | case 3: label = "diff (NW-SE)" ; break; |
692 | default: label = "???" ; break; |
693 | } |
694 | |
695 | *dbgout << json::object // vector |
696 | << "direction" << label |
697 | << "targetMin" << tleft; |
698 | |
699 | outputJsonDbgRemovals(dbgout, axis, seg); |
700 | |
701 | *dbgout << "ranges" ; |
702 | outputJsonDbg(dbgout, seg, axis); |
703 | |
704 | *dbgout << "bestCost" << bestCost |
705 | << "bestVal" << bestVal + tleft |
706 | << json::close; // vectors object |
707 | } |
708 | |
709 | void ShiftCollider::outputJsonDbgRemovals(json * const dbgout, int axis, Segment *seg) |
710 | { |
711 | *dbgout << "removals" << json::array; |
712 | _ranges[axis].jsonDbgOut(seg); |
713 | *dbgout << json::close; // removals array |
714 | } |
715 | |
716 | #endif // !defined GRAPHITE2_NTRACING |
717 | |
718 | |
719 | //// KERN-COLLIDER //// |
720 | |
721 | inline |
722 | static float localmax (float al, float au, float bl, float bu, float x) |
723 | { |
724 | if (al < bl) |
725 | { if (au < bu) return au < x ? au : x; } |
726 | else if (au > bu) return bl < x ? bl : x; |
727 | return x; |
728 | } |
729 | |
730 | inline |
731 | static float localmin(float al, float au, float bl, float bu, float x) |
732 | { |
733 | if (bl > al) |
734 | { if (bu > au) return bl > x ? bl : x; } |
735 | else if (au > bu) return al > x ? al : x; |
736 | return x; |
737 | } |
738 | |
739 | // Return the given edge of the glyph at height y, taking any slant box into account. |
740 | static float get_edge(Segment *seg, const Slot *s, const Position &shift, float y, float width, float margin, bool isRight) |
741 | { |
742 | const GlyphCache &gc = seg->getFace()->glyphs(); |
743 | unsigned short gid = s->gid(); |
744 | float sx = s->origin().x + shift.x; |
745 | float sy = s->origin().y + shift.y; |
746 | uint8 numsub = gc.numSubBounds(gid); |
747 | float res = isRight ? (float)-1e38 : (float)1e38; |
748 | |
749 | if (numsub > 0) |
750 | { |
751 | for (int i = 0; i < numsub; ++i) |
752 | { |
753 | const BBox &sbb = gc.getSubBoundingBBox(gid, i); |
754 | const SlantBox &ssb = gc.getSubBoundingSlantBox(gid, i); |
755 | if (sy + sbb.yi - margin > y + width / 2 || sy + sbb.ya + margin < y - width / 2) |
756 | continue; |
757 | if (isRight) |
758 | { |
759 | float x = sx + sbb.xa + margin; |
760 | if (x > res) |
761 | { |
762 | float td = sx - sy + ssb.da + margin + y; |
763 | float ts = sx + sy + ssb.sa + margin - y; |
764 | x = localmax(td - width / 2, td + width / 2, ts - width / 2, ts + width / 2, x); |
765 | if (x > res) |
766 | res = x; |
767 | } |
768 | } |
769 | else |
770 | { |
771 | float x = sx + sbb.xi - margin; |
772 | if (x < res) |
773 | { |
774 | float td = sx - sy + ssb.di - margin + y; |
775 | float ts = sx + sy + ssb.si - margin - y; |
776 | x = localmin(td - width / 2, td + width / 2, ts - width / 2, ts + width / 2, x); |
777 | if (x < res) |
778 | res = x; |
779 | } |
780 | } |
781 | } |
782 | } |
783 | else |
784 | { |
785 | const BBox &bb = gc.getBoundingBBox(gid); |
786 | const SlantBox &sb = gc.getBoundingSlantBox(gid); |
787 | if (sy + bb.yi - margin > y + width / 2 || sy + bb.ya + margin < y - width / 2) |
788 | return res; |
789 | float td = sx - sy + y; |
790 | float ts = sx + sy - y; |
791 | if (isRight) |
792 | res = localmax(td + sb.da - width / 2, td + sb.da + width / 2, ts + sb.sa - width / 2, ts + sb.sa + width / 2, sx + bb.xa) + margin; |
793 | else |
794 | res = localmin(td + sb.di - width / 2, td + sb.di + width / 2, ts + sb.si - width / 2, ts + sb.si + width / 2, sx + bb.xi) - margin; |
795 | } |
796 | return res; |
797 | } |
798 | |
799 | |
800 | bool KernCollider::initSlot(Segment *seg, Slot *aSlot, const Rect &limit, float margin, |
801 | const Position &currShift, const Position &offsetPrev, int dir, |
802 | float ymin, float ymax, GR_MAYBE_UNUSED json * const dbgout) |
803 | { |
804 | const GlyphCache &gc = seg->getFace()->glyphs(); |
805 | const Slot *base = aSlot; |
806 | // const Slot *last = aSlot; |
807 | const Slot *s; |
808 | int numSlices; |
809 | while (base->attachedTo()) |
810 | base = base->attachedTo(); |
811 | if (margin < 10) margin = 10; |
812 | |
813 | _limit = limit; |
814 | _offsetPrev = offsetPrev; // kern from a previous pass |
815 | |
816 | // Calculate the height of the glyph and how many horizontal slices to use. |
817 | if (_maxy >= 1e37f) |
818 | { |
819 | _sliceWidth = margin / 1.5f; |
820 | _maxy = ymax + margin; |
821 | _miny = ymin - margin; |
822 | numSlices = int((_maxy - _miny + 2) / (_sliceWidth / 1.5f) + 1.f); // +2 helps with rounding errors |
823 | _edges.clear(); |
824 | _edges.insert(_edges.begin(), numSlices, (dir & 1) ? 1e38f : -1e38f); |
825 | _xbound = (dir & 1) ? (float)1e38f : (float)-1e38f; |
826 | } |
827 | else if (_maxy != ymax || _miny != ymin) |
828 | { |
829 | if (_miny != ymin) |
830 | { |
831 | numSlices = int((ymin - margin - _miny) / _sliceWidth - 1); |
832 | _miny += numSlices * _sliceWidth; |
833 | if (numSlices < 0) |
834 | _edges.insert(_edges.begin(), -numSlices, (dir & 1) ? 1e38f : -1e38f); |
835 | else if ((unsigned)numSlices < _edges.size()) // this shouldn't fire since we always grow the range |
836 | { |
837 | Vector<float>::iterator e = _edges.begin(); |
838 | while (numSlices--) |
839 | ++e; |
840 | _edges.erase(_edges.begin(), e); |
841 | } |
842 | } |
843 | if (_maxy != ymax) |
844 | { |
845 | numSlices = int((ymax + margin - _miny) / _sliceWidth + 1); |
846 | _maxy = numSlices * _sliceWidth + _miny; |
847 | if (numSlices > (int)_edges.size()) |
848 | _edges.insert(_edges.end(), numSlices - _edges.size(), (dir & 1) ? 1e38f : -1e38f); |
849 | else if (numSlices < (int)_edges.size()) // this shouldn't fire since we always grow the range |
850 | { |
851 | while ((int)_edges.size() > numSlices) |
852 | _edges.pop_back(); |
853 | } |
854 | } |
855 | goto done; |
856 | } |
857 | numSlices = int(_edges.size()); |
858 | |
859 | #if !defined GRAPHITE2_NTRACING |
860 | // Debugging |
861 | _seg = seg; |
862 | _slotNear.clear(); |
863 | _slotNear.insert(_slotNear.begin(), numSlices, NULL); |
864 | _nearEdges.clear(); |
865 | _nearEdges.insert(_nearEdges.begin(), numSlices, (dir & 1) ? -1e38f : +1e38f); |
866 | #endif |
867 | |
868 | // Determine the trailing edge of each slice (ie, left edge for a RTL glyph). |
869 | for (s = base; s; s = s->nextInCluster(s)) |
870 | { |
871 | SlotCollision *c = seg->collisionInfo(s); |
872 | if (!gc.check(s->gid())) |
873 | return false; |
874 | const BBox &bs = gc.getBoundingBBox(s->gid()); |
875 | float x = s->origin().x + c->shift().x + ((dir & 1) ? bs.xi : bs.xa); |
876 | // Loop over slices. |
877 | // Note smin might not be zero if glyph s is not at the bottom of the cluster; similarly for smax. |
878 | float toffset = c->shift().y - _miny + 1 + s->origin().y; |
879 | int smin = max(0, int((bs.yi + toffset) / _sliceWidth)); |
880 | int smax = min(numSlices - 1, int((bs.ya + toffset) / _sliceWidth + 1)); |
881 | for (int i = smin; i <= smax; ++i) |
882 | { |
883 | float t; |
884 | float y = _miny - 1 + (i + .5f) * _sliceWidth; // vertical center of slice |
885 | if ((dir & 1) && x < _edges[i]) |
886 | { |
887 | t = get_edge(seg, s, c->shift(), y, _sliceWidth, margin, false); |
888 | if (t < _edges[i]) |
889 | { |
890 | _edges[i] = t; |
891 | if (t < _xbound) |
892 | _xbound = t; |
893 | } |
894 | } |
895 | else if (!(dir & 1) && x > _edges[i]) |
896 | { |
897 | t = get_edge(seg, s, c->shift(), y, _sliceWidth, margin, true); |
898 | if (t > _edges[i]) |
899 | { |
900 | _edges[i] = t; |
901 | if (t > _xbound) |
902 | _xbound = t; |
903 | } |
904 | } |
905 | } |
906 | } |
907 | done: |
908 | _mingap = (float)1e37; // less than 1e38 s.t. 1e38-_mingap is really big |
909 | _target = aSlot; |
910 | _margin = margin; |
911 | _currShift = currShift; |
912 | return true; |
913 | } // end of KernCollider::initSlot |
914 | |
915 | |
916 | // Determine how much the target slot needs to kern away from the given slot. |
917 | // In other words, merge information from given slot's position with what the target slot knows |
918 | // about how it can kern. |
919 | // Return false if we know there is no collision, true if we think there might be one. |
920 | bool KernCollider::mergeSlot(Segment *seg, Slot *slot, const Position &currShift, float currSpace, int dir, GR_MAYBE_UNUSED json * const dbgout) |
921 | { |
922 | int rtl = (dir & 1) * 2 - 1; |
923 | if (!seg->getFace()->glyphs().check(slot->gid())) |
924 | return false; |
925 | const Rect &bb = seg->theGlyphBBoxTemporary(slot->gid()); |
926 | const float sx = slot->origin().x + currShift.x; |
927 | float x = (sx + (rtl > 0 ? bb.tr.x : bb.bl.x)) * rtl; |
928 | // this isn't going to reduce _mingap so skip |
929 | if (_hit && x < rtl * (_xbound - _mingap - currSpace)) |
930 | return false; |
931 | |
932 | const float sy = slot->origin().y + currShift.y; |
933 | int smin = max(1, int((bb.bl.y + (1 - _miny + sy)) / _sliceWidth + 1)) - 1; |
934 | int smax = min((int)_edges.size() - 2, int((bb.tr.y + (1 - _miny + sy)) / _sliceWidth + 1)) + 1; |
935 | if (smin > smax) |
936 | return false; |
937 | bool collides = false; |
938 | bool nooverlap = true; |
939 | |
940 | for (int i = smin; i <= smax; ++i) |
941 | { |
942 | float here = _edges[i] * rtl; |
943 | if (here > (float)9e37) |
944 | continue; |
945 | if (!_hit || x > here - _mingap - currSpace) |
946 | { |
947 | float y = (float)(_miny - 1 + (i + .5f) * _sliceWidth); // vertical center of slice |
948 | // 2 * currSpace to account for the space that is already separating them and the space we want to add |
949 | float m = get_edge(seg, slot, currShift, y, _sliceWidth, 0., rtl > 0) * rtl + 2 * currSpace; |
950 | if (m < (float)-8e37) // only true if the glyph has a gap in it |
951 | continue; |
952 | nooverlap = false; |
953 | float t = here - m; |
954 | // _mingap is positive to shrink |
955 | if (t < _mingap || (!_hit && !collides)) |
956 | { |
957 | _mingap = t; |
958 | collides = true; |
959 | } |
960 | #if !defined GRAPHITE2_NTRACING |
961 | // Debugging - remember the closest neighboring edge for this slice. |
962 | if (m > rtl * _nearEdges[i]) |
963 | { |
964 | _slotNear[i] = slot; |
965 | _nearEdges[i] = m * rtl; |
966 | } |
967 | #endif |
968 | } |
969 | else |
970 | nooverlap = false; |
971 | } |
972 | if (nooverlap) |
973 | _mingap = max(_mingap, _xbound - rtl * (currSpace + _margin + x)); |
974 | if (collides && !nooverlap) |
975 | _hit = true; |
976 | return collides | nooverlap; // note that true is not a necessarily reliable value |
977 | |
978 | } // end of KernCollider::mergeSlot |
979 | |
980 | |
981 | // Return the amount to kern by. |
982 | Position KernCollider::resolve(GR_MAYBE_UNUSED Segment *seg, GR_MAYBE_UNUSED Slot *slot, |
983 | int dir, GR_MAYBE_UNUSED json * const dbgout) |
984 | { |
985 | float resultNeeded = (1 - 2 * (dir & 1)) * _mingap; |
986 | // float resultNeeded = (1 - 2 * (dir & 1)) * (_mingap - margin); |
987 | float result = min(_limit.tr.x - _offsetPrev.x, max(resultNeeded, _limit.bl.x - _offsetPrev.x)); |
988 | |
989 | #if !defined GRAPHITE2_NTRACING |
990 | if (dbgout) |
991 | { |
992 | *dbgout << json::object // slot |
993 | << "slot" << objectid(dslot(seg, _target)) |
994 | << "gid" << _target->gid() |
995 | << "limit" << _limit |
996 | << "miny" << _miny |
997 | << "maxy" << _maxy |
998 | << "slicewidth" << _sliceWidth |
999 | << "target" << json::object |
1000 | << "origin" << _target->origin() |
1001 | //<< "currShift" << _currShift |
1002 | << "offsetPrev" << _offsetPrev |
1003 | << "bbox" << seg->theGlyphBBoxTemporary(_target->gid()) |
1004 | << "slantBox" << seg->getFace()->glyphs().slant(_target->gid()) |
1005 | << "fix" << "kern" |
1006 | << json::close; // target object |
1007 | |
1008 | *dbgout << "slices" << json::array; |
1009 | for (int is = 0; is < (int)_edges.size(); is++) |
1010 | { |
1011 | *dbgout << json::flat << json::object |
1012 | << "i" << is |
1013 | << "targetEdge" << _edges[is] |
1014 | << "neighbor" << objectid(dslot(seg, _slotNear[is])) |
1015 | << "nearEdge" << _nearEdges[is] |
1016 | << json::close; |
1017 | } |
1018 | *dbgout << json::close; // slices array |
1019 | |
1020 | *dbgout |
1021 | << "xbound" << _xbound |
1022 | << "minGap" << _mingap |
1023 | << "needed" << resultNeeded |
1024 | << "result" << result |
1025 | << "stillBad" << (result != resultNeeded) |
1026 | << json::close; // slot object |
1027 | } |
1028 | #endif |
1029 | |
1030 | return Position(result, 0.); |
1031 | |
1032 | } // end of KernCollider::resolve |
1033 | |
1034 | void KernCollider::shift(const Position &mv, int dir) |
1035 | { |
1036 | for (Vector<float>::iterator e = _edges.begin(); e != _edges.end(); ++e) |
1037 | *e += mv.x; |
1038 | _xbound += (1 - 2 * (dir & 1)) * mv.x; |
1039 | } |
1040 | |
1041 | //// SLOT-COLLISION //// |
1042 | |
1043 | // Initialize the collision attributes for the given slot. |
1044 | SlotCollision::SlotCollision(Segment *seg, Slot *slot) |
1045 | { |
1046 | initFromSlot(seg, slot); |
1047 | } |
1048 | |
1049 | void SlotCollision::initFromSlot(Segment *seg, Slot *slot) |
1050 | { |
1051 | // Initialize slot attributes from glyph attributes. |
1052 | // The order here must match the order in the grcompiler code, |
1053 | // GrcSymbolTable::AssignInternalGlyphAttrIDs. |
1054 | uint16 gid = slot->gid(); |
1055 | uint16 aCol = seg->silf()->aCollision(); // flags attr ID |
1056 | const GlyphFace * glyphFace = seg->getFace()->glyphs().glyphSafe(gid); |
1057 | if (!glyphFace) |
1058 | return; |
1059 | const sparse &p = glyphFace->attrs(); |
1060 | _flags = p[aCol]; |
1061 | _limit = Rect(Position(int16(p[aCol+1]), int16(p[aCol+2])), |
1062 | Position(int16(p[aCol+3]), int16(p[aCol+4]))); |
1063 | _margin = p[aCol+5]; |
1064 | _marginWt = p[aCol+6]; |
1065 | |
1066 | _seqClass = p[aCol+7]; |
1067 | _seqProxClass = p[aCol+8]; |
1068 | _seqOrder = p[aCol+9]; |
1069 | _seqAboveXoff = p[aCol+10]; |
1070 | _seqAboveWt = p[aCol+11]; |
1071 | _seqBelowXlim = p[aCol+12]; |
1072 | _seqBelowWt = p[aCol+13]; |
1073 | _seqValignHt = p[aCol+14]; |
1074 | _seqValignWt = p[aCol+15]; |
1075 | |
1076 | // These attributes do not have corresponding glyph attribute: |
1077 | _exclGlyph = 0; |
1078 | _exclOffset = Position(0, 0); |
1079 | } |
1080 | |
1081 | float SlotCollision::getKern(int dir) const |
1082 | { |
1083 | if ((_flags & SlotCollision::COLL_KERN) != 0) |
1084 | return float(_shift.x * ((dir & 1) ? -1 : 1)); |
1085 | else |
1086 | return 0; |
1087 | } |
1088 | |
1089 | bool SlotCollision::ignore() const |
1090 | { |
1091 | return ((flags() & SlotCollision::COLL_IGNORE) || (flags() & SlotCollision::COLL_ISSPACE)); |
1092 | } |
1093 | |