Coverage Report

Created: 2025-07-07 10:01

/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
}