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