/work/workdir/UnpackedTarball/graphite/src/Pass.cpp
Line | Count | Source (jump to first uncovered line) |
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 "inc/Main.h" |
28 | | #include "inc/debug.h" |
29 | | #include "inc/Endian.h" |
30 | | #include "inc/Pass.h" |
31 | | #include <cstring> |
32 | | #include <cstdlib> |
33 | | #include <cassert> |
34 | | #include <cmath> |
35 | | #include "inc/Segment.h" |
36 | | #include "inc/Code.h" |
37 | | #include "inc/Rule.h" |
38 | | #include "inc/Error.h" |
39 | | #include "inc/Collider.h" |
40 | | |
41 | | using namespace graphite2; |
42 | | using vm::Machine; |
43 | | typedef Machine::Code Code; |
44 | | |
45 | | enum KernCollison |
46 | | { |
47 | | None = 0, |
48 | | CrossSpace = 1, |
49 | | InWord = 2, |
50 | | reserved = 3 |
51 | | }; |
52 | | |
53 | | Pass::Pass() |
54 | 0 | : m_silf(0), |
55 | 0 | m_cols(0), |
56 | 0 | m_rules(0), |
57 | 0 | m_ruleMap(0), |
58 | 0 | m_startStates(0), |
59 | 0 | m_transitions(0), |
60 | 0 | m_states(0), |
61 | 0 | m_codes(0), |
62 | 0 | m_progs(0), |
63 | 0 | m_numCollRuns(0), |
64 | 0 | m_kernColls(0), |
65 | 0 | m_iMaxLoop(0), |
66 | 0 | m_numGlyphs(0), |
67 | 0 | m_numRules(0), |
68 | 0 | m_numStates(0), |
69 | 0 | m_numTransition(0), |
70 | 0 | m_numSuccess(0), |
71 | 0 | m_successStart(0), |
72 | 0 | m_numColumns(0), |
73 | 0 | m_minPreCtxt(0), |
74 | 0 | m_maxPreCtxt(0), |
75 | 0 | m_colThreshold(0), |
76 | 0 | m_isReverseDir(false) |
77 | 0 | { |
78 | 0 | } |
79 | | |
80 | | Pass::~Pass() |
81 | 0 | { |
82 | 0 | free(m_cols); |
83 | 0 | free(m_startStates); |
84 | 0 | free(m_transitions); |
85 | 0 | free(m_states); |
86 | 0 | free(m_ruleMap); |
87 | |
|
88 | 0 | if (m_rules) delete [] m_rules; |
89 | 0 | if (m_codes) delete [] m_codes; |
90 | 0 | free(m_progs); |
91 | 0 | } |
92 | | |
93 | | bool Pass::readPass(const byte * const pass_start, size_t pass_length, size_t subtable_base, |
94 | | GR_MAYBE_UNUSED Face & face, passtype pt, GR_MAYBE_UNUSED uint32 version, Error &e) |
95 | 0 | { |
96 | 0 | const byte * p = pass_start, |
97 | 0 | * const pass_end = p + pass_length; |
98 | 0 | size_t numRanges; |
99 | |
|
100 | 0 | if (e.test(pass_length < 40, E_BADPASSLENGTH)) return face.error(e); |
101 | | // Read in basic values |
102 | 0 | const byte flags = be::read<byte>(p); |
103 | 0 | if (e.test((flags & 0x1f) && |
104 | 0 | (pt < PASS_TYPE_POSITIONING || !m_silf->aCollision() || !face.glyphs().hasBoxes() || !(m_silf->flags() & 0x20)), |
105 | 0 | E_BADCOLLISIONPASS)) |
106 | 0 | return face.error(e); |
107 | 0 | m_numCollRuns = flags & 0x7; |
108 | 0 | m_kernColls = (flags >> 3) & 0x3; |
109 | 0 | m_isReverseDir = (flags >> 5) & 0x1; |
110 | 0 | m_iMaxLoop = be::read<byte>(p); |
111 | 0 | if (m_iMaxLoop < 1) m_iMaxLoop = 1; |
112 | 0 | be::skip<byte>(p,2); // skip maxContext & maxBackup |
113 | 0 | m_numRules = be::read<uint16>(p); |
114 | 0 | if (e.test(!m_numRules && m_numCollRuns == 0, E_BADEMPTYPASS)) return face.error(e); |
115 | 0 | be::skip<uint16>(p); // fsmOffset - not sure why we would want this |
116 | 0 | const byte * const pcCode = pass_start + be::read<uint32>(p) - subtable_base, |
117 | 0 | * const rcCode = pass_start + be::read<uint32>(p) - subtable_base, |
118 | 0 | * const aCode = pass_start + be::read<uint32>(p) - subtable_base; |
119 | 0 | be::skip<uint32>(p); |
120 | 0 | m_numStates = be::read<uint16>(p); |
121 | 0 | m_numTransition = be::read<uint16>(p); |
122 | 0 | m_numSuccess = be::read<uint16>(p); |
123 | 0 | m_numColumns = be::read<uint16>(p); |
124 | 0 | numRanges = be::read<uint16>(p); |
125 | 0 | be::skip<uint16>(p, 3); // skip searchRange, entrySelector & rangeShift. |
126 | 0 | assert(p - pass_start == 40); |
127 | | // Perform some sanity checks. |
128 | 0 | if ( e.test(m_numTransition > m_numStates, E_BADNUMTRANS) |
129 | 0 | || e.test(m_numSuccess > m_numStates, E_BADNUMSUCCESS) |
130 | 0 | || e.test(m_numSuccess + m_numTransition < m_numStates, E_BADNUMSTATES) |
131 | 0 | || e.test(m_numRules && numRanges == 0, E_NORANGES) |
132 | 0 | || e.test(m_numColumns > 0x7FFF, E_BADNUMCOLUMNS)) |
133 | 0 | return face.error(e); |
134 | | |
135 | 0 | m_successStart = m_numStates - m_numSuccess; |
136 | | // test for beyond end - 1 to account for reading uint16 |
137 | 0 | if (e.test(p + numRanges * 6 - 2 > pass_end, E_BADPASSLENGTH)) return face.error(e); |
138 | 0 | m_numGlyphs = be::peek<uint16>(p + numRanges * 6 - 4) + 1; |
139 | | // Calculate the start of various arrays. |
140 | 0 | const byte * const ranges = p; |
141 | 0 | be::skip<uint16>(p, numRanges*3); |
142 | 0 | const byte * const o_rule_map = p; |
143 | 0 | be::skip<uint16>(p, m_numSuccess + 1); |
144 | | |
145 | | // More sanity checks |
146 | 0 | if (e.test(reinterpret_cast<const byte *>(o_rule_map + m_numSuccess*sizeof(uint16)) > pass_end |
147 | 0 | || p > pass_end, E_BADRULEMAPLEN)) |
148 | 0 | return face.error(e); |
149 | 0 | const size_t numEntries = be::peek<uint16>(o_rule_map + m_numSuccess*sizeof(uint16)); |
150 | 0 | const byte * const rule_map = p; |
151 | 0 | be::skip<uint16>(p, numEntries); |
152 | |
|
153 | 0 | if (e.test(p + 2*sizeof(uint8) > pass_end, E_BADPASSLENGTH)) return face.error(e); |
154 | 0 | m_minPreCtxt = be::read<uint8>(p); |
155 | 0 | m_maxPreCtxt = be::read<uint8>(p); |
156 | 0 | if (e.test(m_minPreCtxt > m_maxPreCtxt, E_BADCTXTLENBOUNDS)) return face.error(e); |
157 | 0 | const byte * const start_states = p; |
158 | 0 | be::skip<int16>(p, m_maxPreCtxt - m_minPreCtxt + 1); |
159 | 0 | const uint16 * const sort_keys = reinterpret_cast<const uint16 *>(p); |
160 | 0 | be::skip<uint16>(p, m_numRules); |
161 | 0 | const byte * const precontext = p; |
162 | 0 | be::skip<byte>(p, m_numRules); |
163 | |
|
164 | 0 | if (e.test(p + sizeof(uint16) + sizeof(uint8) > pass_end, E_BADCTXTLENS)) return face.error(e); |
165 | 0 | m_colThreshold = be::read<uint8>(p); |
166 | 0 | if (m_colThreshold == 0) m_colThreshold = 10; // A default |
167 | 0 | const size_t pass_constraint_len = be::read<uint16>(p); |
168 | |
|
169 | 0 | const uint16 * const o_constraint = reinterpret_cast<const uint16 *>(p); |
170 | 0 | be::skip<uint16>(p, m_numRules + 1); |
171 | 0 | const uint16 * const o_actions = reinterpret_cast<const uint16 *>(p); |
172 | 0 | be::skip<uint16>(p, m_numRules + 1); |
173 | 0 | const byte * const states = p; |
174 | 0 | if (e.test(2u*m_numTransition*m_numColumns >= (unsigned)(pass_end - p), E_BADPASSLENGTH) |
175 | 0 | || e.test(p >= pass_end, E_BADPASSLENGTH)) |
176 | 0 | return face.error(e); |
177 | 0 | be::skip<int16>(p, m_numTransition*m_numColumns); |
178 | 0 | be::skip<uint8>(p); |
179 | 0 | if (e.test(p != pcCode, E_BADPASSCCODEPTR)) return face.error(e); |
180 | 0 | be::skip<byte>(p, pass_constraint_len); |
181 | 0 | if (e.test(p != rcCode, E_BADRULECCODEPTR) |
182 | 0 | || e.test(size_t(rcCode - pcCode) != pass_constraint_len, E_BADCCODELEN)) return face.error(e); |
183 | 0 | be::skip<byte>(p, be::peek<uint16>(o_constraint + m_numRules)); |
184 | 0 | if (e.test(p != aCode, E_BADACTIONCODEPTR)) return face.error(e); |
185 | 0 | be::skip<byte>(p, be::peek<uint16>(o_actions + m_numRules)); |
186 | | |
187 | | // We should be at the end or within the pass |
188 | 0 | if (e.test(p > pass_end, E_BADPASSLENGTH)) return face.error(e); |
189 | | |
190 | | // Load the pass constraint if there is one. |
191 | 0 | if (pass_constraint_len) |
192 | 0 | { |
193 | 0 | face.error_context(face.error_context() + 1); |
194 | 0 | m_cPConstraint = vm::Machine::Code(true, pcCode, pcCode + pass_constraint_len, |
195 | 0 | precontext[0], be::peek<uint16>(sort_keys), *m_silf, face, PASS_TYPE_UNKNOWN); |
196 | 0 | if (e.test(!m_cPConstraint, E_OUTOFMEM) |
197 | 0 | || e.test(m_cPConstraint.status() != Code::loaded, +m_cPConstraint.status() + E_CODEFAILURE)) |
198 | 0 | return face.error(e); |
199 | 0 | face.error_context(face.error_context() - 1); |
200 | 0 | } |
201 | 0 | if (m_numRules) |
202 | 0 | { |
203 | 0 | if (!readRanges(ranges, numRanges, e)) return face.error(e); |
204 | 0 | if (!readRules(rule_map, numEntries, precontext, sort_keys, |
205 | 0 | o_constraint, rcCode, o_actions, aCode, face, pt, e)) return false; |
206 | 0 | } |
207 | | #ifdef GRAPHITE2_TELEMETRY |
208 | | telemetry::category _states_cat(face.tele.states); |
209 | | #endif |
210 | 0 | return m_numRules ? readStates(start_states, states, o_rule_map, face, e) : true; |
211 | 0 | } |
212 | | |
213 | | |
214 | | bool Pass::readRules(const byte * rule_map, const size_t num_entries, |
215 | | const byte *precontext, const uint16 * sort_key, |
216 | | const uint16 * o_constraint, const byte *rc_data, |
217 | | const uint16 * o_action, const byte * ac_data, |
218 | | Face & face, passtype pt, Error &e) |
219 | 0 | { |
220 | 0 | const byte * const ac_data_end = ac_data + be::peek<uint16>(o_action + m_numRules); |
221 | 0 | const byte * const rc_data_end = rc_data + be::peek<uint16>(o_constraint + m_numRules); |
222 | |
|
223 | 0 | precontext += m_numRules; |
224 | 0 | sort_key += m_numRules; |
225 | 0 | o_constraint += m_numRules; |
226 | 0 | o_action += m_numRules; |
227 | | |
228 | | // Load rules. |
229 | 0 | const byte * ac_begin = 0, * rc_begin = 0, |
230 | 0 | * ac_end = ac_data + be::peek<uint16>(o_action), |
231 | 0 | * rc_end = rc_data + be::peek<uint16>(o_constraint); |
232 | | |
233 | | // Allocate pools |
234 | 0 | m_rules = new Rule [m_numRules]; |
235 | 0 | m_codes = new Code [m_numRules*2]; |
236 | 0 | int totalSlots = 0; |
237 | 0 | const uint16 *tsort = sort_key; |
238 | 0 | for (int i = 0; i < m_numRules; ++i) |
239 | 0 | totalSlots += be::peek<uint16>(--tsort); |
240 | 0 | const size_t prog_pool_sz = vm::Machine::Code::estimateCodeDataOut(ac_end - ac_data + rc_end - rc_data, 2 * m_numRules, totalSlots); |
241 | 0 | m_progs = gralloc<byte>(prog_pool_sz); |
242 | 0 | byte * prog_pool_free = m_progs, |
243 | 0 | * prog_pool_end = m_progs + prog_pool_sz; |
244 | 0 | if (e.test(!(m_rules && m_codes && m_progs), E_OUTOFMEM)) return face.error(e); |
245 | | |
246 | 0 | Rule * r = m_rules + m_numRules - 1; |
247 | 0 | for (size_t n = m_numRules; r >= m_rules; --n, --r, ac_end = ac_begin, rc_end = rc_begin) |
248 | 0 | { |
249 | 0 | face.error_context((face.error_context() & 0xFFFF00) + EC_ARULE + int((n - 1) << 24)); |
250 | 0 | r->preContext = *--precontext; |
251 | 0 | r->sort = be::peek<uint16>(--sort_key); |
252 | | #ifndef NDEBUG |
253 | | r->rule_idx = uint16(n - 1); |
254 | | #endif |
255 | 0 | if (r->sort > 63 || r->preContext >= r->sort || r->preContext > m_maxPreCtxt || r->preContext < m_minPreCtxt) |
256 | 0 | return false; |
257 | 0 | ac_begin = ac_data + be::peek<uint16>(--o_action); |
258 | 0 | --o_constraint; |
259 | 0 | rc_begin = be::peek<uint16>(o_constraint) ? rc_data + be::peek<uint16>(o_constraint) : rc_end; |
260 | |
|
261 | 0 | if (ac_begin > ac_end || ac_begin > ac_data_end || ac_end > ac_data_end |
262 | 0 | || rc_begin > rc_end || rc_begin > rc_data_end || rc_end > rc_data_end |
263 | 0 | || vm::Machine::Code::estimateCodeDataOut(ac_end - ac_begin + rc_end - rc_begin, 2, r->sort) > size_t(prog_pool_end - prog_pool_free)) |
264 | 0 | return false; |
265 | 0 | r->action = new (m_codes+n*2-2) vm::Machine::Code(false, ac_begin, ac_end, r->preContext, r->sort, *m_silf, face, pt, &prog_pool_free); |
266 | 0 | r->constraint = new (m_codes+n*2-1) vm::Machine::Code(true, rc_begin, rc_end, r->preContext, r->sort, *m_silf, face, pt, &prog_pool_free); |
267 | |
|
268 | 0 | if (e.test(!r->action || !r->constraint, E_OUTOFMEM) |
269 | 0 | || e.test(r->action->status() != Code::loaded, +r->action->status() + E_CODEFAILURE) |
270 | 0 | || e.test(r->constraint->status() != Code::loaded, +r->constraint->status() + E_CODEFAILURE) |
271 | 0 | || e.test(!r->constraint->immutable(), E_MUTABLECCODE)) |
272 | 0 | return face.error(e); |
273 | 0 | } |
274 | | |
275 | 0 | byte * const moved_progs = prog_pool_free > m_progs ? static_cast<byte *>(realloc(m_progs, prog_pool_free - m_progs)) : 0; |
276 | 0 | if (e.test(!moved_progs, E_OUTOFMEM)) |
277 | 0 | { |
278 | 0 | free(m_progs); |
279 | 0 | m_progs = 0; |
280 | 0 | return face.error(e); |
281 | 0 | } |
282 | | |
283 | 0 | if (moved_progs != m_progs) |
284 | 0 | { |
285 | 0 | for (Code * c = m_codes, * const ce = c + m_numRules*2; c != ce; ++c) |
286 | 0 | { |
287 | 0 | c->externalProgramMoved(moved_progs - m_progs); |
288 | 0 | } |
289 | 0 | m_progs = moved_progs; |
290 | 0 | } |
291 | | |
292 | | // Load the rule entries map |
293 | 0 | face.error_context((face.error_context() & 0xFFFF00) + EC_APASS); |
294 | | //TODO: Coverity: 1315804: FORWARD_NULL |
295 | 0 | RuleEntry * re = m_ruleMap = gralloc<RuleEntry>(num_entries); |
296 | 0 | if (e.test(!re, E_OUTOFMEM)) return face.error(e); |
297 | 0 | for (size_t n = num_entries; n; --n, ++re) |
298 | 0 | { |
299 | 0 | const ptrdiff_t rn = be::read<uint16>(rule_map); |
300 | 0 | if (e.test(rn >= m_numRules, E_BADRULENUM)) return face.error(e); |
301 | 0 | re->rule = m_rules + rn; |
302 | 0 | } |
303 | | |
304 | 0 | return true; |
305 | 0 | } |
306 | | |
307 | 0 | static int cmpRuleEntry(const void *a, const void *b) { return (*(RuleEntry *)a < *(RuleEntry *)b ? -1 : |
308 | 0 | (*(RuleEntry *)b < *(RuleEntry *)a ? 1 : 0)); } |
309 | | |
310 | | bool Pass::readStates(const byte * starts, const byte *states, const byte * o_rule_map, GR_MAYBE_UNUSED Face & face, Error &e) |
311 | 0 | { |
312 | | #ifdef GRAPHITE2_TELEMETRY |
313 | | telemetry::category _states_cat(face.tele.starts); |
314 | | #endif |
315 | 0 | m_startStates = gralloc<uint16>(m_maxPreCtxt - m_minPreCtxt + 1); |
316 | | #ifdef GRAPHITE2_TELEMETRY |
317 | | telemetry::set_category(face.tele.states); |
318 | | #endif |
319 | 0 | m_states = gralloc<State>(m_numStates); |
320 | | #ifdef GRAPHITE2_TELEMETRY |
321 | | telemetry::set_category(face.tele.transitions); |
322 | | #endif |
323 | 0 | m_transitions = gralloc<uint16>(m_numTransition * m_numColumns); |
324 | |
|
325 | 0 | if (e.test(!m_startStates || !m_states || !m_transitions, E_OUTOFMEM)) return face.error(e); |
326 | | // load start states |
327 | 0 | for (uint16 * s = m_startStates, |
328 | 0 | * const s_end = s + m_maxPreCtxt - m_minPreCtxt + 1; s != s_end; ++s) |
329 | 0 | { |
330 | 0 | *s = be::read<uint16>(starts); |
331 | 0 | if (e.test(*s >= m_numStates, E_BADSTATE)) |
332 | 0 | { |
333 | 0 | face.error_context((face.error_context() & 0xFFFF00) + EC_ASTARTS + int((s - m_startStates) << 24)); |
334 | 0 | return face.error(e); // true; |
335 | 0 | } |
336 | 0 | } |
337 | | |
338 | | // load state transition table. |
339 | 0 | for (uint16 * t = m_transitions, |
340 | 0 | * const t_end = t + m_numTransition*m_numColumns; t != t_end; ++t) |
341 | 0 | { |
342 | 0 | *t = be::read<uint16>(states); |
343 | 0 | if (e.test(*t >= m_numStates, E_BADSTATE)) |
344 | 0 | { |
345 | 0 | face.error_context((face.error_context() & 0xFFFF00) + EC_ATRANS + int(((t - m_transitions) / m_numColumns) << 8)); |
346 | 0 | return face.error(e); |
347 | 0 | } |
348 | 0 | } |
349 | | |
350 | 0 | State * s = m_states, |
351 | 0 | * const success_begin = m_states + m_numStates - m_numSuccess; |
352 | 0 | const RuleEntry * rule_map_end = m_ruleMap + be::peek<uint16>(o_rule_map + m_numSuccess*sizeof(uint16)); |
353 | 0 | for (size_t n = m_numStates; n; --n, ++s) |
354 | 0 | { |
355 | 0 | RuleEntry * const begin = s < success_begin ? 0 : m_ruleMap + be::read<uint16>(o_rule_map), |
356 | 0 | * const end = s < success_begin ? 0 : m_ruleMap + be::peek<uint16>(o_rule_map); |
357 | |
|
358 | 0 | if (e.test(begin >= rule_map_end || end > rule_map_end || begin > end, E_BADRULEMAPPING)) |
359 | 0 | { |
360 | 0 | face.error_context((face.error_context() & 0xFFFF00) + EC_ARULEMAP + int(n << 24)); |
361 | 0 | return face.error(e); |
362 | 0 | } |
363 | 0 | s->rules = begin; |
364 | 0 | s->rules_end = (end - begin <= FiniteStateMachine::MAX_RULES)? end : |
365 | 0 | begin + FiniteStateMachine::MAX_RULES; |
366 | 0 | if (begin) // keep UBSan happy can't call qsort with null begin |
367 | 0 | qsort(begin, end - begin, sizeof(RuleEntry), &cmpRuleEntry); |
368 | 0 | } |
369 | | |
370 | 0 | return true; |
371 | 0 | } |
372 | | |
373 | | bool Pass::readRanges(const byte * ranges, size_t num_ranges, Error &e) |
374 | 0 | { |
375 | 0 | m_cols = gralloc<uint16>(m_numGlyphs); |
376 | 0 | if (e.test(!m_cols, E_OUTOFMEM)) return false; |
377 | 0 | memset(m_cols, 0xFF, m_numGlyphs * sizeof(uint16)); |
378 | 0 | for (size_t n = num_ranges; n; --n) |
379 | 0 | { |
380 | 0 | uint16 * ci = m_cols + be::read<uint16>(ranges), |
381 | 0 | * ci_end = m_cols + be::read<uint16>(ranges) + 1, |
382 | 0 | col = be::read<uint16>(ranges); |
383 | |
|
384 | 0 | if (e.test(ci >= ci_end || ci_end > m_cols+m_numGlyphs || col >= m_numColumns, E_BADRANGE)) |
385 | 0 | return false; |
386 | | |
387 | | // A glyph must only belong to one column at a time |
388 | 0 | while (ci != ci_end && *ci == 0xffff) |
389 | 0 | *ci++ = col; |
390 | |
|
391 | 0 | if (e.test(ci != ci_end, E_BADRANGE)) |
392 | 0 | return false; |
393 | 0 | } |
394 | 0 | return true; |
395 | 0 | } |
396 | | |
397 | | |
398 | | bool Pass::runGraphite(vm::Machine & m, FiniteStateMachine & fsm, bool reverse) const |
399 | 0 | { |
400 | 0 | Slot *s = m.slotMap().segment.first(); |
401 | 0 | if (!s || !testPassConstraint(m)) return true; |
402 | 0 | if (reverse) |
403 | 0 | { |
404 | 0 | m.slotMap().segment.reverseSlots(); |
405 | 0 | s = m.slotMap().segment.first(); |
406 | 0 | } |
407 | 0 | if (m_numRules) |
408 | 0 | { |
409 | 0 | Slot *currHigh = s->next(); |
410 | |
|
411 | | #if !defined GRAPHITE2_NTRACING |
412 | | if (fsm.dbgout) *fsm.dbgout << "rules" << json::array; |
413 | | json::closer rules_array_closer(fsm.dbgout); |
414 | | #endif |
415 | |
|
416 | 0 | m.slotMap().highwater(currHigh); |
417 | 0 | int lc = m_iMaxLoop; |
418 | 0 | do |
419 | 0 | { |
420 | 0 | findNDoRule(s, m, fsm); |
421 | 0 | if (m.status() != Machine::finished) return false; |
422 | 0 | if (s && (s == m.slotMap().highwater() || m.slotMap().highpassed() || --lc == 0)) { |
423 | 0 | if (!lc) |
424 | 0 | s = m.slotMap().highwater(); |
425 | 0 | lc = m_iMaxLoop; |
426 | 0 | if (s) |
427 | 0 | m.slotMap().highwater(s->next()); |
428 | 0 | } |
429 | 0 | } while (s); |
430 | 0 | } |
431 | | //TODO: Use enums for flags |
432 | 0 | const bool collisions = m_numCollRuns || m_kernColls; |
433 | |
|
434 | 0 | if (!collisions || !m.slotMap().segment.hasCollisionInfo()) |
435 | 0 | return true; |
436 | | |
437 | 0 | if (m_numCollRuns) |
438 | 0 | { |
439 | 0 | if (!(m.slotMap().segment.flags() & Segment::SEG_INITCOLLISIONS)) |
440 | 0 | { |
441 | 0 | m.slotMap().segment.positionSlots(0, 0, 0, m.slotMap().dir(), true); |
442 | | // m.slotMap().segment.flags(m.slotMap().segment.flags() | Segment::SEG_INITCOLLISIONS); |
443 | 0 | } |
444 | 0 | if (!collisionShift(&m.slotMap().segment, m.slotMap().dir(), fsm.dbgout)) |
445 | 0 | return false; |
446 | 0 | } |
447 | 0 | if ((m_kernColls) && !collisionKern(&m.slotMap().segment, m.slotMap().dir(), fsm.dbgout)) |
448 | 0 | return false; |
449 | 0 | if (collisions && !collisionFinish(&m.slotMap().segment, fsm.dbgout)) |
450 | 0 | return false; |
451 | 0 | return true; |
452 | 0 | } |
453 | | |
454 | | bool Pass::runFSM(FiniteStateMachine& fsm, Slot * slot) const |
455 | 0 | { |
456 | 0 | fsm.reset(slot, m_maxPreCtxt); |
457 | 0 | if (fsm.slots.context() < m_minPreCtxt) |
458 | 0 | return false; |
459 | | |
460 | 0 | uint16 state = m_startStates[m_maxPreCtxt - fsm.slots.context()]; |
461 | 0 | uint8 free_slots = SlotMap::MAX_SLOTS; |
462 | 0 | do |
463 | 0 | { |
464 | 0 | fsm.slots.pushSlot(slot); |
465 | 0 | if (slot->gid() >= m_numGlyphs |
466 | 0 | || m_cols[slot->gid()] == 0xffffU |
467 | 0 | || --free_slots == 0 |
468 | 0 | || state >= m_numTransition) |
469 | 0 | return free_slots != 0; |
470 | | |
471 | 0 | const uint16 * transitions = m_transitions + state*m_numColumns; |
472 | 0 | state = transitions[m_cols[slot->gid()]]; |
473 | 0 | if (state >= m_successStart) |
474 | 0 | fsm.rules.accumulate_rules(m_states[state]); |
475 | |
|
476 | 0 | slot = slot->next(); |
477 | 0 | } while (state != 0 && slot); |
478 | | |
479 | 0 | fsm.slots.pushSlot(slot); |
480 | 0 | return true; |
481 | 0 | } |
482 | | |
483 | | #if !defined GRAPHITE2_NTRACING |
484 | | |
485 | | inline |
486 | | Slot * input_slot(const SlotMap & slots, const int n) |
487 | | { |
488 | | Slot * s = slots[slots.context() + n]; |
489 | | if (!s->isCopied()) return s; |
490 | | |
491 | | return s->prev() ? s->prev()->next() : (s->next() ? s->next()->prev() : slots.segment.last()); |
492 | | } |
493 | | |
494 | | inline |
495 | | Slot * output_slot(const SlotMap & slots, const int n) |
496 | | { |
497 | | Slot * s = slots[slots.context() + n - 1]; |
498 | | return s ? s->next() : slots.segment.first(); |
499 | | } |
500 | | |
501 | | #endif //!defined GRAPHITE2_NTRACING |
502 | | |
503 | | void Pass::findNDoRule(Slot * & slot, Machine &m, FiniteStateMachine & fsm) const |
504 | 0 | { |
505 | 0 | assert(slot); |
506 | |
|
507 | 0 | if (runFSM(fsm, slot)) |
508 | 0 | { |
509 | | // Search for the first rule which passes the constraint |
510 | 0 | const RuleEntry * r = fsm.rules.begin(), |
511 | 0 | * const re = fsm.rules.end(); |
512 | 0 | while (r != re && !testConstraint(*r->rule, m)) |
513 | 0 | { |
514 | 0 | ++r; |
515 | 0 | if (m.status() != Machine::finished) |
516 | 0 | return; |
517 | 0 | } |
518 | | |
519 | | #if !defined GRAPHITE2_NTRACING |
520 | | if (fsm.dbgout) |
521 | | { |
522 | | if (fsm.rules.size() != 0) |
523 | | { |
524 | | *fsm.dbgout << json::item << json::object; |
525 | | dumpRuleEventConsidered(fsm, *r); |
526 | | if (r != re) |
527 | | { |
528 | | const int adv = doAction(r->rule->action, slot, m); |
529 | | dumpRuleEventOutput(fsm, *r->rule, slot); |
530 | | if (r->rule->action->deletes()) fsm.slots.collectGarbage(slot); |
531 | | adjustSlot(adv, slot, fsm.slots); |
532 | | *fsm.dbgout << "cursor" << objectid(dslot(&fsm.slots.segment, slot)) |
533 | | << json::close; // Close RuelEvent object |
534 | | |
535 | | return; |
536 | | } |
537 | | else |
538 | | { |
539 | | *fsm.dbgout << json::close // close "considered" array |
540 | | << "output" << json::null |
541 | | << "cursor" << objectid(dslot(&fsm.slots.segment, slot->next())) |
542 | | << json::close; |
543 | | } |
544 | | } |
545 | | } |
546 | | else |
547 | | #endif |
548 | 0 | { |
549 | 0 | if (r != re) |
550 | 0 | { |
551 | 0 | const int adv = doAction(r->rule->action, slot, m); |
552 | 0 | if (m.status() != Machine::finished) return; |
553 | 0 | if (r->rule->action->deletes()) fsm.slots.collectGarbage(slot); |
554 | 0 | adjustSlot(adv, slot, fsm.slots); |
555 | 0 | return; |
556 | 0 | } |
557 | 0 | } |
558 | 0 | } |
559 | | |
560 | 0 | slot = slot->next(); |
561 | 0 | return; |
562 | 0 | } |
563 | | |
564 | | #if !defined GRAPHITE2_NTRACING |
565 | | |
566 | | void Pass::dumpRuleEventConsidered(const FiniteStateMachine & fsm, const RuleEntry & re) const |
567 | | { |
568 | | *fsm.dbgout << "considered" << json::array; |
569 | | for (const RuleEntry *r = fsm.rules.begin(); r != &re; ++r) |
570 | | { |
571 | | if (r->rule->preContext > fsm.slots.context()) |
572 | | continue; |
573 | | *fsm.dbgout << json::flat << json::object |
574 | | << "id" << r->rule - m_rules |
575 | | << "failed" << true |
576 | | << "input" << json::flat << json::object |
577 | | << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, -r->rule->preContext))) |
578 | | << "length" << r->rule->sort |
579 | | << json::close // close "input" |
580 | | << json::close; // close Rule object |
581 | | } |
582 | | } |
583 | | |
584 | | |
585 | | void Pass::dumpRuleEventOutput(const FiniteStateMachine & fsm, const Rule & r, Slot * const last_slot) const |
586 | | { |
587 | | *fsm.dbgout << json::item << json::flat << json::object |
588 | | << "id" << &r - m_rules |
589 | | << "failed" << false |
590 | | << "input" << json::flat << json::object |
591 | | << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, 0))) |
592 | | << "length" << r.sort - r.preContext |
593 | | << json::close // close "input" |
594 | | << json::close // close Rule object |
595 | | << json::close // close considered array |
596 | | << "output" << json::object |
597 | | << "range" << json::flat << json::object |
598 | | << "start" << objectid(dslot(&fsm.slots.segment, input_slot(fsm.slots, 0))) |
599 | | << "end" << objectid(dslot(&fsm.slots.segment, last_slot)) |
600 | | << json::close // close "input" |
601 | | << "slots" << json::array; |
602 | | const Position rsb_prepos = last_slot ? last_slot->origin() : fsm.slots.segment.advance(); |
603 | | fsm.slots.segment.positionSlots(0, 0, 0, fsm.slots.segment.currdir()); |
604 | | |
605 | | for(Slot * slot = output_slot(fsm.slots, 0); slot != last_slot; slot = slot->next()) |
606 | | *fsm.dbgout << dslot(&fsm.slots.segment, slot); |
607 | | *fsm.dbgout << json::close // close "slots" |
608 | | << "postshift" << (last_slot ? last_slot->origin() : fsm.slots.segment.advance()) - rsb_prepos |
609 | | << json::close; // close "output" object |
610 | | |
611 | | } |
612 | | |
613 | | #endif |
614 | | |
615 | | |
616 | | inline |
617 | | bool Pass::testPassConstraint(Machine & m) const |
618 | 0 | { |
619 | 0 | if (!m_cPConstraint) return true; |
620 | | |
621 | 0 | assert(m_cPConstraint.constraint()); |
622 | |
|
623 | 0 | m.slotMap().reset(*m.slotMap().segment.first(), 0); |
624 | 0 | m.slotMap().pushSlot(m.slotMap().segment.first()); |
625 | 0 | vm::slotref * map = m.slotMap().begin(); |
626 | 0 | const uint32 ret = m_cPConstraint.run(m, map); |
627 | |
|
628 | | #if !defined GRAPHITE2_NTRACING |
629 | | json * const dbgout = m.slotMap().segment.getFace()->logger(); |
630 | | if (dbgout) |
631 | | *dbgout << "constraint" << (ret && m.status() == Machine::finished); |
632 | | #endif |
633 | |
|
634 | 0 | return ret && m.status() == Machine::finished; |
635 | 0 | } |
636 | | |
637 | | |
638 | | bool Pass::testConstraint(const Rule & r, Machine & m) const |
639 | 0 | { |
640 | 0 | const uint16 curr_context = m.slotMap().context(); |
641 | 0 | if (unsigned(r.sort + curr_context - r.preContext) > m.slotMap().size() |
642 | 0 | || curr_context - r.preContext < 0) return false; |
643 | | |
644 | 0 | vm::slotref * map = m.slotMap().begin() + curr_context - r.preContext; |
645 | 0 | if (map[r.sort - 1] == 0) |
646 | 0 | return false; |
647 | | |
648 | 0 | if (!*r.constraint) return true; |
649 | 0 | assert(r.constraint->constraint()); |
650 | 0 | for (int n = r.sort; n && map; --n, ++map) |
651 | 0 | { |
652 | 0 | if (!*map) continue; |
653 | 0 | const int32 ret = r.constraint->run(m, map); |
654 | 0 | if (!ret || m.status() != Machine::finished) |
655 | 0 | return false; |
656 | 0 | } |
657 | | |
658 | 0 | return true; |
659 | 0 | } |
660 | | |
661 | | |
662 | | void SlotMap::collectGarbage(Slot * &aSlot) |
663 | 0 | { |
664 | 0 | for(Slot **s = begin(), *const *const se = end() - 1; s != se; ++s) { |
665 | 0 | Slot *& slot = *s; |
666 | 0 | if(slot && (slot->isDeleted() || slot->isCopied())) |
667 | 0 | { |
668 | 0 | if (slot == aSlot) |
669 | 0 | aSlot = slot->prev() ? slot->prev() : slot->next(); |
670 | 0 | segment.freeSlot(slot); |
671 | 0 | } |
672 | 0 | } |
673 | 0 | } |
674 | | |
675 | | |
676 | | |
677 | | int Pass::doAction(const Code *codeptr, Slot * & slot_out, vm::Machine & m) const |
678 | 0 | { |
679 | 0 | assert(codeptr); |
680 | 0 | if (!*codeptr) return 0; |
681 | 0 | SlotMap & smap = m.slotMap(); |
682 | 0 | vm::slotref * map = &smap[smap.context()]; |
683 | 0 | smap.highpassed(false); |
684 | |
|
685 | 0 | int32 ret = codeptr->run(m, map); |
686 | |
|
687 | 0 | if (m.status() != Machine::finished) |
688 | 0 | { |
689 | 0 | slot_out = NULL; |
690 | 0 | smap.highwater(0); |
691 | 0 | return 0; |
692 | 0 | } |
693 | | |
694 | 0 | slot_out = *map; |
695 | 0 | return ret; |
696 | 0 | } |
697 | | |
698 | | |
699 | | void Pass::adjustSlot(int delta, Slot * & slot_out, SlotMap & smap) const |
700 | 0 | { |
701 | 0 | if (!slot_out) |
702 | 0 | { |
703 | 0 | if (smap.highpassed() || slot_out == smap.highwater()) |
704 | 0 | { |
705 | 0 | slot_out = smap.segment.last(); |
706 | 0 | ++delta; |
707 | 0 | if (!smap.highwater() || smap.highwater() == slot_out) |
708 | 0 | smap.highpassed(false); |
709 | 0 | } |
710 | 0 | else |
711 | 0 | { |
712 | 0 | slot_out = smap.segment.first(); |
713 | 0 | --delta; |
714 | 0 | } |
715 | 0 | } |
716 | 0 | if (delta < 0) |
717 | 0 | { |
718 | 0 | while (++delta <= 0 && slot_out) |
719 | 0 | { |
720 | 0 | slot_out = slot_out->prev(); |
721 | 0 | if (smap.highpassed() && smap.highwater() == slot_out) |
722 | 0 | smap.highpassed(false); |
723 | 0 | } |
724 | 0 | } |
725 | 0 | else if (delta > 0) |
726 | 0 | { |
727 | 0 | while (--delta >= 0 && slot_out) |
728 | 0 | { |
729 | 0 | if (slot_out == smap.highwater() && slot_out) |
730 | 0 | smap.highpassed(true); |
731 | 0 | slot_out = slot_out->next(); |
732 | 0 | } |
733 | 0 | } |
734 | 0 | } |
735 | | |
736 | | bool Pass::collisionShift(Segment *seg, int dir, json * const dbgout) const |
737 | 0 | { |
738 | 0 | ShiftCollider shiftcoll(dbgout); |
739 | | // bool isfirst = true; |
740 | 0 | bool hasCollisions = false; |
741 | 0 | Slot *start = seg->first(); // turn on collision fixing for the first slot |
742 | 0 | Slot *end = NULL; |
743 | 0 | bool moved = false; |
744 | |
|
745 | | #if !defined GRAPHITE2_NTRACING |
746 | | if (dbgout) |
747 | | *dbgout << "collisions" << json::array |
748 | | << json::flat << json::object << "num-loops" << m_numCollRuns << json::close; |
749 | | #endif |
750 | |
|
751 | 0 | while (start) |
752 | 0 | { |
753 | | #if !defined GRAPHITE2_NTRACING |
754 | | if (dbgout) *dbgout << json::object << "phase" << "1" << "moves" << json::array; |
755 | | #endif |
756 | 0 | hasCollisions = false; |
757 | 0 | end = NULL; |
758 | | // phase 1 : position shiftable glyphs, ignoring kernable glyphs |
759 | 0 | for (Slot *s = start; s; s = s->next()) |
760 | 0 | { |
761 | 0 | const SlotCollision * c = seg->collisionInfo(s); |
762 | 0 | if (start && (c->flags() & (SlotCollision::COLL_FIX | SlotCollision::COLL_KERN)) == SlotCollision::COLL_FIX |
763 | 0 | && !resolveCollisions(seg, s, start, shiftcoll, false, dir, moved, hasCollisions, dbgout)) |
764 | 0 | return false; |
765 | 0 | if (s != start && (c->flags() & SlotCollision::COLL_END)) |
766 | 0 | { |
767 | 0 | end = s->next(); |
768 | 0 | break; |
769 | 0 | } |
770 | 0 | } |
771 | | |
772 | | #if !defined GRAPHITE2_NTRACING |
773 | | if (dbgout) |
774 | | *dbgout << json::close << json::close; // phase-1 |
775 | | #endif |
776 | | |
777 | | // phase 2 : loop until happy. |
778 | 0 | for (int i = 0; i < m_numCollRuns - 1; ++i) |
779 | 0 | { |
780 | 0 | if (hasCollisions || moved) |
781 | 0 | { |
782 | |
|
783 | | #if !defined GRAPHITE2_NTRACING |
784 | | if (dbgout) |
785 | | *dbgout << json::object << "phase" << "2a" << "loop" << i << "moves" << json::array; |
786 | | #endif |
787 | | // phase 2a : if any shiftable glyphs are in collision, iterate backwards, |
788 | | // fixing them and ignoring other non-collided glyphs. Note that this handles ONLY |
789 | | // glyphs that are actually in collision from phases 1 or 2b, and working backwards |
790 | | // has the intended effect of breaking logjams. |
791 | 0 | if (hasCollisions) |
792 | 0 | { |
793 | 0 | hasCollisions = false; |
794 | | #if 0 |
795 | | moved = true; |
796 | | for (Slot *s = start; s != end; s = s->next()) |
797 | | { |
798 | | SlotCollision * c = seg->collisionInfo(s); |
799 | | c->setShift(Position(0, 0)); |
800 | | } |
801 | | #endif |
802 | 0 | Slot *lend = end ? end->prev() : seg->last(); |
803 | 0 | Slot *lstart = start->prev(); |
804 | 0 | for (Slot *s = lend; s != lstart; s = s->prev()) |
805 | 0 | { |
806 | 0 | SlotCollision * c = seg->collisionInfo(s); |
807 | 0 | if (start && (c->flags() & (SlotCollision::COLL_FIX | SlotCollision::COLL_KERN | SlotCollision::COLL_ISCOL)) |
808 | 0 | == (SlotCollision::COLL_FIX | SlotCollision::COLL_ISCOL)) // ONLY if this glyph is still colliding |
809 | 0 | { |
810 | 0 | if (!resolveCollisions(seg, s, lend, shiftcoll, true, dir, moved, hasCollisions, dbgout)) |
811 | 0 | return false; |
812 | 0 | c->setFlags(c->flags() | SlotCollision::COLL_TEMPLOCK); |
813 | 0 | } |
814 | 0 | } |
815 | 0 | } |
816 | | |
817 | | #if !defined GRAPHITE2_NTRACING |
818 | | if (dbgout) |
819 | | *dbgout << json::close << json::close // phase 2a |
820 | | << json::object << "phase" << "2b" << "loop" << i << "moves" << json::array; |
821 | | #endif |
822 | | |
823 | | // phase 2b : redo basic diacritic positioning pass for ALL glyphs. Each successive loop adjusts |
824 | | // glyphs from their current adjusted position, which has the effect of gradually minimizing the |
825 | | // resulting adjustment; ie, the final result will be gradually closer to the original location. |
826 | | // Also it allows more flexibility in the final adjustment, since it is moving along the |
827 | | // possible 8 vectors from successively different starting locations. |
828 | 0 | if (moved) |
829 | 0 | { |
830 | 0 | moved = false; |
831 | 0 | for (Slot *s = start; s != end; s = s->next()) |
832 | 0 | { |
833 | 0 | SlotCollision * c = seg->collisionInfo(s); |
834 | 0 | if (start && (c->flags() & (SlotCollision::COLL_FIX | SlotCollision::COLL_TEMPLOCK |
835 | 0 | | SlotCollision::COLL_KERN)) == SlotCollision::COLL_FIX |
836 | 0 | && !resolveCollisions(seg, s, start, shiftcoll, false, dir, moved, hasCollisions, dbgout)) |
837 | 0 | return false; |
838 | 0 | else if (c->flags() & SlotCollision::COLL_TEMPLOCK) |
839 | 0 | c->setFlags(c->flags() & ~SlotCollision::COLL_TEMPLOCK); |
840 | 0 | } |
841 | 0 | } |
842 | | // if (!hasCollisions) // no, don't leave yet because phase 2b will continue to improve things |
843 | | // break; |
844 | | #if !defined GRAPHITE2_NTRACING |
845 | | if (dbgout) |
846 | | *dbgout << json::close << json::close; // phase 2 |
847 | | #endif |
848 | 0 | } |
849 | 0 | } |
850 | 0 | if (!end) |
851 | 0 | break; |
852 | 0 | start = NULL; |
853 | 0 | for (Slot *s = end->prev(); s; s = s->next()) |
854 | 0 | { |
855 | 0 | if (seg->collisionInfo(s)->flags() & SlotCollision::COLL_START) |
856 | 0 | { |
857 | 0 | start = s; |
858 | 0 | break; |
859 | 0 | } |
860 | 0 | } |
861 | 0 | } |
862 | 0 | return true; |
863 | 0 | } |
864 | | |
865 | | bool Pass::collisionKern(Segment *seg, int dir, json * const dbgout) const |
866 | 0 | { |
867 | 0 | Slot *start = seg->first(); |
868 | 0 | float ymin = 1e38f; |
869 | 0 | float ymax = -1e38f; |
870 | 0 | const GlyphCache &gc = seg->getFace()->glyphs(); |
871 | | |
872 | | // phase 3 : handle kerning of clusters |
873 | | #if !defined GRAPHITE2_NTRACING |
874 | | if (dbgout) |
875 | | *dbgout << json::object << "phase" << "3" << "moves" << json::array; |
876 | | #endif |
877 | |
|
878 | 0 | for (Slot *s = seg->first(); s; s = s->next()) |
879 | 0 | { |
880 | 0 | if (!gc.check(s->gid())) |
881 | 0 | return false; |
882 | 0 | const SlotCollision * c = seg->collisionInfo(s); |
883 | 0 | const Rect &bbox = seg->theGlyphBBoxTemporary(s->gid()); |
884 | 0 | float y = s->origin().y + c->shift().y; |
885 | 0 | if (!(c->flags() & SlotCollision::COLL_ISSPACE)) |
886 | 0 | { |
887 | 0 | ymax = max(y + bbox.tr.y, ymax); |
888 | 0 | ymin = min(y + bbox.bl.y, ymin); |
889 | 0 | } |
890 | 0 | if (start && (c->flags() & (SlotCollision::COLL_KERN | SlotCollision::COLL_FIX)) |
891 | 0 | == (SlotCollision::COLL_KERN | SlotCollision::COLL_FIX)) |
892 | 0 | resolveKern(seg, s, start, dir, ymin, ymax, dbgout); |
893 | 0 | if (c->flags() & SlotCollision::COLL_END) |
894 | 0 | start = NULL; |
895 | 0 | if (c->flags() & SlotCollision::COLL_START) |
896 | 0 | start = s; |
897 | 0 | } |
898 | | |
899 | | #if !defined GRAPHITE2_NTRACING |
900 | | if (dbgout) |
901 | | *dbgout << json::close << json::close; // phase 3 |
902 | | #endif |
903 | 0 | return true; |
904 | 0 | } |
905 | | |
906 | | bool Pass::collisionFinish(Segment *seg, GR_MAYBE_UNUSED json * const dbgout) const |
907 | 0 | { |
908 | 0 | for (Slot *s = seg->first(); s; s = s->next()) |
909 | 0 | { |
910 | 0 | SlotCollision *c = seg->collisionInfo(s); |
911 | 0 | if (c->shift().x != 0 || c->shift().y != 0) |
912 | 0 | { |
913 | 0 | const Position newOffset = c->shift(); |
914 | 0 | const Position nullPosition(0, 0); |
915 | 0 | c->setOffset(newOffset + c->offset()); |
916 | 0 | c->setShift(nullPosition); |
917 | 0 | } |
918 | 0 | } |
919 | | // seg->positionSlots(); |
920 | |
|
921 | | #if !defined GRAPHITE2_NTRACING |
922 | | if (dbgout) |
923 | | *dbgout << json::close; |
924 | | #endif |
925 | 0 | return true; |
926 | 0 | } |
927 | | |
928 | | // Can slot s be kerned, or is it attached to something that can be kerned? |
929 | | static bool inKernCluster(Segment *seg, Slot *s) |
930 | 0 | { |
931 | 0 | SlotCollision *c = seg->collisionInfo(s); |
932 | 0 | if (c->flags() & SlotCollision::COLL_KERN /** && c->flags() & SlotCollision::COLL_FIX **/ ) |
933 | 0 | return true; |
934 | 0 | while (s->attachedTo()) |
935 | 0 | { |
936 | 0 | s = s->attachedTo(); |
937 | 0 | c = seg->collisionInfo(s); |
938 | 0 | if (c->flags() & SlotCollision::COLL_KERN /** && c->flags() & SlotCollision::COLL_FIX **/ ) |
939 | 0 | return true; |
940 | 0 | } |
941 | 0 | return false; |
942 | 0 | } |
943 | | |
944 | | // Fix collisions for the given slot. |
945 | | // Return true if everything was fixed, false if there are still collisions remaining. |
946 | | // isRev means be we are processing backwards. |
947 | | bool Pass::resolveCollisions(Segment *seg, Slot *slotFix, Slot *start, |
948 | | ShiftCollider &coll, GR_MAYBE_UNUSED bool isRev, int dir, bool &moved, bool &hasCol, |
949 | | json * const dbgout) const |
950 | 0 | { |
951 | 0 | Slot * nbor; // neighboring slot |
952 | 0 | SlotCollision *cFix = seg->collisionInfo(slotFix); |
953 | 0 | if (!coll.initSlot(seg, slotFix, cFix->limit(), cFix->margin(), cFix->marginWt(), |
954 | 0 | cFix->shift(), cFix->offset(), dir, dbgout)) |
955 | 0 | return false; |
956 | 0 | bool collides = false; |
957 | | // When we're processing forward, ignore kernable glyphs that preceed the target glyph. |
958 | | // When processing backward, don't ignore these until we pass slotFix. |
959 | 0 | bool ignoreForKern = !isRev; |
960 | 0 | bool rtl = dir & 1; |
961 | 0 | Slot *base = slotFix; |
962 | 0 | while (base->attachedTo()) |
963 | 0 | base = base->attachedTo(); |
964 | 0 | Position zero(0., 0.); |
965 | | |
966 | | // Look for collisions with the neighboring glyphs. |
967 | 0 | for (nbor = start; nbor; nbor = isRev ? nbor->prev() : nbor->next()) |
968 | 0 | { |
969 | 0 | SlotCollision *cNbor = seg->collisionInfo(nbor); |
970 | 0 | bool sameCluster = nbor->isChildOf(base); |
971 | 0 | if (nbor != slotFix // don't process if this is the slot of interest |
972 | 0 | && !(cNbor->ignore()) // don't process if ignoring |
973 | 0 | && (nbor == base || sameCluster // process if in the same cluster as slotFix |
974 | 0 | || !inKernCluster(seg, nbor)) // or this cluster is not to be kerned |
975 | | // || (rtl ^ ignoreForKern)) // or it comes before(ltr) or after(rtl) |
976 | 0 | && (!isRev // if processing forwards then good to merge otherwise only: |
977 | 0 | || !(cNbor->flags() & SlotCollision::COLL_FIX) // merge in immovable stuff |
978 | 0 | || ((cNbor->flags() & SlotCollision::COLL_KERN) && !sameCluster) // ignore other kernable clusters |
979 | 0 | || (cNbor->flags() & SlotCollision::COLL_ISCOL)) // test against other collided glyphs |
980 | 0 | && !coll.mergeSlot(seg, nbor, cNbor, cNbor->shift(), !ignoreForKern, sameCluster, collides, false, dbgout)) |
981 | 0 | return false; |
982 | 0 | else if (nbor == slotFix) |
983 | | // Switching sides of this glyph - if we were ignoring kernable stuff before, don't anymore. |
984 | 0 | ignoreForKern = !ignoreForKern; |
985 | | |
986 | 0 | if (nbor != start && (cNbor->flags() & (isRev ? SlotCollision::COLL_START : SlotCollision::COLL_END))) |
987 | 0 | break; |
988 | 0 | } |
989 | 0 | bool isCol = false; |
990 | 0 | if (collides || cFix->shift().x != 0.f || cFix->shift().y != 0.f) |
991 | 0 | { |
992 | 0 | Position shift = coll.resolve(seg, isCol, dbgout); |
993 | | // isCol has been set to true if a collision remains. |
994 | 0 | if (std::fabs(shift.x) < 1e38f && std::fabs(shift.y) < 1e38f) |
995 | 0 | { |
996 | 0 | if (sqr(shift.x-cFix->shift().x) + sqr(shift.y-cFix->shift().y) >= m_colThreshold * m_colThreshold) |
997 | 0 | moved = true; |
998 | 0 | cFix->setShift(shift); |
999 | 0 | if (slotFix->firstChild()) |
1000 | 0 | { |
1001 | 0 | Rect bbox; |
1002 | 0 | Position here = slotFix->origin() + shift; |
1003 | 0 | float clusterMin = here.x; |
1004 | 0 | slotFix->firstChild()->finalise(seg, NULL, here, bbox, 0, clusterMin, rtl, false); |
1005 | 0 | } |
1006 | 0 | } |
1007 | 0 | } |
1008 | 0 | else |
1009 | 0 | { |
1010 | | // This glyph is not colliding with anything. |
1011 | | #if !defined GRAPHITE2_NTRACING |
1012 | | if (dbgout) |
1013 | | { |
1014 | | *dbgout << json::object |
1015 | | << "missed" << objectid(dslot(seg, slotFix)); |
1016 | | coll.outputJsonDbg(dbgout, seg, -1); |
1017 | | *dbgout << json::close; |
1018 | | } |
1019 | | #endif |
1020 | 0 | } |
1021 | | |
1022 | | // Set the is-collision flag bit. |
1023 | 0 | if (isCol) |
1024 | 0 | { cFix->setFlags(cFix->flags() | SlotCollision::COLL_ISCOL | SlotCollision::COLL_KNOWN); } |
1025 | 0 | else |
1026 | 0 | { cFix->setFlags((cFix->flags() & ~SlotCollision::COLL_ISCOL) | SlotCollision::COLL_KNOWN); } |
1027 | 0 | hasCol |= isCol; |
1028 | 0 | return true; |
1029 | 0 | } |
1030 | | |
1031 | | float Pass::resolveKern(Segment *seg, Slot *slotFix, GR_MAYBE_UNUSED Slot *start, int dir, |
1032 | | float &ymin, float &ymax, json *const dbgout) const |
1033 | 0 | { |
1034 | 0 | Slot *nbor; // neighboring slot |
1035 | 0 | float currSpace = 0.; |
1036 | 0 | bool collides = false; |
1037 | 0 | unsigned int space_count = 0; |
1038 | 0 | Slot *base = slotFix; |
1039 | 0 | while (base->attachedTo()) |
1040 | 0 | base = base->attachedTo(); |
1041 | 0 | SlotCollision *cFix = seg->collisionInfo(base); |
1042 | 0 | const GlyphCache &gc = seg->getFace()->glyphs(); |
1043 | 0 | const Rect &bbb = seg->theGlyphBBoxTemporary(slotFix->gid()); |
1044 | 0 | const float by = slotFix->origin().y + cFix->shift().y; |
1045 | |
|
1046 | 0 | if (base != slotFix) |
1047 | 0 | { |
1048 | 0 | cFix->setFlags(cFix->flags() | SlotCollision::COLL_KERN | SlotCollision::COLL_FIX); |
1049 | 0 | return 0; |
1050 | 0 | } |
1051 | 0 | bool seenEnd = (cFix->flags() & SlotCollision::COLL_END) != 0; |
1052 | 0 | bool isInit = false; |
1053 | 0 | KernCollider coll(dbgout); |
1054 | |
|
1055 | 0 | ymax = max(by + bbb.tr.y, ymax); |
1056 | 0 | ymin = min(by + bbb.bl.y, ymin); |
1057 | 0 | for (nbor = slotFix->next(); nbor; nbor = nbor->next()) |
1058 | 0 | { |
1059 | 0 | if (nbor->isChildOf(base)) |
1060 | 0 | continue; |
1061 | 0 | if (!gc.check(nbor->gid())) |
1062 | 0 | return 0.; |
1063 | 0 | const Rect &bb = seg->theGlyphBBoxTemporary(nbor->gid()); |
1064 | 0 | SlotCollision *cNbor = seg->collisionInfo(nbor); |
1065 | 0 | if ((bb.bl.y == 0.f && bb.tr.y == 0.f) || (cNbor->flags() & SlotCollision::COLL_ISSPACE)) |
1066 | 0 | { |
1067 | 0 | if (m_kernColls == InWord) |
1068 | 0 | break; |
1069 | | // Add space for a space glyph. |
1070 | 0 | currSpace += nbor->advance(); |
1071 | 0 | ++space_count; |
1072 | 0 | } |
1073 | 0 | else |
1074 | 0 | { |
1075 | 0 | space_count = 0; |
1076 | 0 | if (nbor != slotFix && !cNbor->ignore()) |
1077 | 0 | { |
1078 | 0 | seenEnd = true; |
1079 | 0 | if (!isInit) |
1080 | 0 | { |
1081 | 0 | if (!coll.initSlot(seg, slotFix, cFix->limit(), cFix->margin(), |
1082 | 0 | cFix->shift(), cFix->offset(), dir, ymin, ymax, dbgout)) |
1083 | 0 | return 0.; |
1084 | 0 | isInit = true; |
1085 | 0 | } |
1086 | 0 | collides |= coll.mergeSlot(seg, nbor, cNbor->shift(), currSpace, dir, dbgout); |
1087 | 0 | } |
1088 | 0 | } |
1089 | 0 | if (cNbor->flags() & SlotCollision::COLL_END) |
1090 | 0 | { |
1091 | 0 | if (seenEnd && space_count < 2) |
1092 | 0 | break; |
1093 | 0 | else |
1094 | 0 | seenEnd = true; |
1095 | 0 | } |
1096 | 0 | } |
1097 | 0 | if (collides) |
1098 | 0 | { |
1099 | 0 | Position mv = coll.resolve(seg, slotFix, dir, dbgout); |
1100 | 0 | coll.shift(mv, dir); |
1101 | 0 | Position delta = slotFix->advancePos() + mv - cFix->shift(); |
1102 | 0 | slotFix->advance(delta); |
1103 | 0 | cFix->setShift(mv); |
1104 | 0 | return mv.x; |
1105 | 0 | } |
1106 | 0 | return 0.; |
1107 | 0 | } |