Coverage Report

Created: 2026-06-30 11:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/workdir/UnpackedTarball/graphite/src/Justifier.cpp
Line
Count
Source
1
// SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later
2
// Copyright 2012, SIL International, All rights reserved.
3
4
5
#include "inc/Segment.h"
6
#include "graphite2/Font.h"
7
#include "inc/debug.h"
8
#include "inc/CharInfo.h"
9
#include "inc/Slot.h"
10
#include "inc/Main.h"
11
#include <cmath>
12
13
using namespace graphite2;
14
15
class JustifyTotal {
16
public:
17
0
    JustifyTotal() : m_numGlyphs(0), m_tStretch(0), m_tShrink(0), m_tStep(0), m_tWeight(0) {}
18
    void accumulate(Slot *s, Segment *seg, int level);
19
0
    int weight() const { return m_tWeight; }
20
21
    CLASS_NEW_DELETE
22
23
private:
24
    int m_numGlyphs;
25
    int m_tStretch;
26
    int m_tShrink;
27
    int m_tStep;
28
    int m_tWeight;
29
};
30
31
void JustifyTotal::accumulate(Slot *s, Segment *seg, int level)
32
0
{
33
0
    ++m_numGlyphs;
34
0
    m_tStretch += s->getJustify(seg, level, 0);
35
0
    m_tShrink += s->getJustify(seg, level, 1);
36
0
    m_tStep += s->getJustify(seg, level, 2);
37
0
    m_tWeight += s->getJustify(seg, level, 3);
38
0
}
39
40
float Segment::justify(Slot *pSlot, const Font *font, float width, GR_MAYBE_UNUSED justFlags jflags, Slot *pFirst, Slot *pLast)
41
0
{
42
0
    Slot *end = last();
43
0
    float currWidth = 0.0;
44
0
    const float scale = font ? font->scale() : 1.0f;
45
0
    Position res;
46
47
0
    if (width < 0 && !(silf()->flags()))
48
0
        return width;
49
50
0
    if ((m_dir & 1) != m_silf->dir() && m_silf->bidiPass() != m_silf->numPasses())
51
0
    {
52
0
        reverseSlots();
53
0
        std::swap(pFirst, pLast);
54
0
    }
55
0
    if (!pFirst) pFirst = pSlot;
56
0
    while (!pFirst->isBase()) pFirst = pFirst->attachedTo();
57
0
    if (!pLast) pLast = last();
58
0
    while (!pLast->isBase()) pLast = pLast->attachedTo();
59
0
    const float base = pFirst->origin().x / scale;
60
0
    width = width / scale;
61
0
    if ((jflags & gr_justEndInline) == 0)
62
0
    {
63
0
        while (pLast != pFirst && pLast)
64
0
        {
65
0
            Rect bbox = theGlyphBBoxTemporary(pLast->glyph());
66
0
            if (bbox.bl.x != 0.f || bbox.bl.y != 0.f || bbox.tr.x != 0.f || bbox.tr.y == 0.f)
67
0
                break;
68
0
            pLast = pLast->prev();
69
0
        }
70
0
    }
71
72
0
    if (pLast)
73
0
        end = pLast->nextSibling();
74
0
    if (pFirst)
75
0
        pFirst = pFirst->nextSibling();
76
77
0
    int icount = 0;
78
0
    int numLevels = silf()->numJustLevels();
79
0
    if (!numLevels)
80
0
    {
81
0
        for (Slot *s = pSlot; s && s != end; s = s->nextSibling())
82
0
        {
83
0
            CharInfo *c = charinfo(s->before());
84
0
            if (isWhitespace(c->unicodeChar()))
85
0
            {
86
0
                s->setJustify(this, 0, 3, 1);
87
0
                s->setJustify(this, 0, 2, 1);
88
0
                s->setJustify(this, 0, 0, -1);
89
0
                ++icount;
90
0
            }
91
0
        }
92
0
        if (!icount)
93
0
        {
94
0
            for (Slot *s = pSlot; s && s != end; s = s->nextSibling())
95
0
            {
96
0
                s->setJustify(this, 0, 3, 1);
97
0
                s->setJustify(this, 0, 2, 1);
98
0
                s->setJustify(this, 0, 0, -1);
99
0
            }
100
0
        }
101
0
        ++numLevels;
102
0
    }
103
104
0
    Vector<JustifyTotal> stats(numLevels);
105
0
    for (Slot *s = pFirst; s && s != end; s = s->nextSibling())
106
0
    {
107
0
        float w = s->origin().x / scale + s->advance() - base;
108
0
        if (w > currWidth) currWidth = w;
109
0
        for (int j = 0; j < numLevels; ++j)
110
0
            stats[j].accumulate(s, this, j);
111
0
        s->just(0);
112
0
    }
113
114
0
    for (int i = (width < 0.0f) ? -1 : numLevels - 1; i >= 0; --i)
115
0
    {
116
0
        float diff;
117
0
        float error = 0.;
118
0
        float diffpw;
119
0
        int tWeight = stats[i].weight();
120
0
        if (tWeight == 0) continue;
121
122
0
        do {
123
0
            error = 0.;
124
0
            diff = width - currWidth;
125
0
            diffpw = diff / tWeight;
126
0
            tWeight = 0;
127
0
            for (Slot *s = pFirst; s && s != end; s = s->nextSibling()) // don't include final glyph
128
0
            {
129
0
                int w = s->getJustify(this, i, 3);
130
0
                float pref = diffpw * w + error;
131
0
                int step = s->getJustify(this, i, 2);
132
0
                if (!step) step = 1;        // handle lazy font developers
133
0
                if (pref > 0)
134
0
                {
135
0
                    float max = uint16(s->getJustify(this, i, 0));
136
0
                    if (i == 0) max -= s->just();
137
0
                    if (pref > max) pref = max;
138
0
                    else tWeight += w;
139
0
                }
140
0
                else
141
0
                {
142
0
                    float max = uint16(s->getJustify(this, i, 1));
143
0
                    if (i == 0) max += s->just();
144
0
                    if (-pref > max) pref = -max;
145
0
                    else tWeight += w;
146
0
                }
147
0
                int actual = int(pref / step) * step;
148
149
0
                if (actual)
150
0
                {
151
0
                    error += diffpw * w - actual;
152
0
                    if (i == 0)
153
0
                        s->just(s->just() + actual);
154
0
                    else
155
0
                        s->setJustify(this, i, 4, actual);
156
0
                }
157
0
            }
158
0
            currWidth += diff - error;
159
0
        } while (i == 0 && int(std::abs(error)) > 0 && tWeight);
160
0
    }
161
162
0
    Slot *oldFirst = m_first;
163
0
    Slot *oldLast = m_last;
164
0
    if (silf()->flags() & 1)
165
0
    {
166
0
        m_first = pSlot = addLineEnd(pSlot);
167
0
        m_last = pLast = addLineEnd(end);
168
0
        if (!m_first || !m_last) return -1.0;
169
0
    }
170
0
    else
171
0
    {
172
0
        m_first = pSlot;
173
0
        m_last = pLast;
174
0
    }
175
176
    // run justification passes here
177
#if !defined GRAPHITE2_NTRACING
178
    json * const dbgout = m_face->logger();
179
    if (dbgout)
180
        *dbgout << json::object
181
                    << "justifies"  << objectid(this)
182
                    << "passes"     << json::array;
183
#endif
184
185
0
    if (m_silf->justificationPass() != m_silf->positionPass() && (width >= 0.f || (silf()->flags() & 1)))
186
0
        m_silf->runGraphite(this, m_silf->justificationPass(), m_silf->positionPass());
187
188
#if !defined GRAPHITE2_NTRACING
189
    if (dbgout)
190
    {
191
        *dbgout     << json::item << json::close; // Close up the passes array
192
        positionSlots(NULL, pSlot, pLast, m_dir);
193
        Slot *lEnd = pLast->nextSibling();
194
        *dbgout << "output" << json::array;
195
        for(Slot * t = pSlot; t != lEnd; t = t->next())
196
            *dbgout     << dslot(this, t);
197
        *dbgout         << json::close << json::close;
198
    }
199
#endif
200
201
0
    res = positionSlots(font, pSlot, pLast, m_dir);
202
203
0
    if (silf()->flags() & 1)
204
0
    {
205
0
        if (m_first)
206
0
            delLineEnd(m_first);
207
0
        if (m_last)
208
0
            delLineEnd(m_last);
209
0
    }
210
0
    m_first = oldFirst;
211
0
    m_last = oldLast;
212
213
0
    if ((m_dir & 1) != m_silf->dir() && m_silf->bidiPass() != m_silf->numPasses())
214
0
        reverseSlots();
215
0
    return res.x;
216
0
}
217
218
Slot *Segment::addLineEnd(Slot *nSlot)
219
0
{
220
0
    Slot *eSlot = newSlot();
221
0
    if (!eSlot) return NULL;
222
0
    const uint16 gid = silf()->endLineGlyphid();
223
0
    const GlyphFace * theGlyph = m_face->glyphs().glyphSafe(gid);
224
0
    eSlot->setGlyph(this, gid, theGlyph);
225
0
    if (nSlot)
226
0
    {
227
0
        eSlot->next(nSlot);
228
0
        eSlot->prev(nSlot->prev());
229
0
        nSlot->prev(eSlot);
230
0
        eSlot->before(nSlot->before());
231
0
        if (eSlot->prev())
232
0
            eSlot->after(eSlot->prev()->after());
233
0
        else
234
0
            eSlot->after(nSlot->before());
235
0
    }
236
0
    else
237
0
    {
238
0
        nSlot = m_last;
239
0
        eSlot->prev(nSlot);
240
0
        nSlot->next(eSlot);
241
0
        eSlot->after(eSlot->prev()->after());
242
0
        eSlot->before(nSlot->after());
243
0
    }
244
0
    return eSlot;
245
0
}
246
247
void Segment::delLineEnd(Slot *s)
248
0
{
249
0
    Slot *nSlot = s->next();
250
0
    if (nSlot)
251
0
    {
252
0
        nSlot->prev(s->prev());
253
0
        if (s->prev())
254
0
            s->prev()->next(nSlot);
255
0
    }
256
0
    else
257
0
        s->prev()->next(NULL);
258
0
    freeSlot(s);
259
0
}