godot/thirdparty/graphite/src/Justifier.cpp

260 lines
7.3 KiB
C++

// SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later
// Copyright 2012, SIL International, All rights reserved.
#include "inc/Segment.h"
#include "graphite2/Font.h"
#include "inc/debug.h"
#include "inc/CharInfo.h"
#include "inc/Slot.h"
#include "inc/Main.h"
#include <cmath>
using namespace graphite2;
class JustifyTotal {
public:
JustifyTotal() : m_numGlyphs(0), m_tStretch(0), m_tShrink(0), m_tStep(0), m_tWeight(0) {}
void accumulate(Slot *s, Segment *seg, int level);
int weight() const { return m_tWeight; }
CLASS_NEW_DELETE
private:
int m_numGlyphs;
int m_tStretch;
int m_tShrink;
int m_tStep;
int m_tWeight;
};
void JustifyTotal::accumulate(Slot *s, Segment *seg, int level)
{
++m_numGlyphs;
m_tStretch += s->getJustify(seg, level, 0);
m_tShrink += s->getJustify(seg, level, 1);
m_tStep += s->getJustify(seg, level, 2);
m_tWeight += s->getJustify(seg, level, 3);
}
float Segment::justify(Slot *pSlot, const Font *font, float width, GR_MAYBE_UNUSED justFlags jflags, Slot *pFirst, Slot *pLast)
{
Slot *end = last();
float currWidth = 0.0;
const float scale = font ? font->scale() : 1.0f;
Position res;
if (width < 0 && !(silf()->flags()))
return width;
if ((m_dir & 1) != m_silf->dir() && m_silf->bidiPass() != m_silf->numPasses())
{
reverseSlots();
std::swap(pFirst, pLast);
}
if (!pFirst) pFirst = pSlot;
while (!pFirst->isBase()) pFirst = pFirst->attachedTo();
if (!pLast) pLast = last();
while (!pLast->isBase()) pLast = pLast->attachedTo();
const float base = pFirst->origin().x / scale;
width = width / scale;
if ((jflags & gr_justEndInline) == 0)
{
while (pLast != pFirst && pLast)
{
Rect bbox = theGlyphBBoxTemporary(pLast->glyph());
if (bbox.bl.x != 0.f || bbox.bl.y != 0.f || bbox.tr.x != 0.f || bbox.tr.y == 0.f)
break;
pLast = pLast->prev();
}
}
if (pLast)
end = pLast->nextSibling();
if (pFirst)
pFirst = pFirst->nextSibling();
int icount = 0;
int numLevels = silf()->numJustLevels();
if (!numLevels)
{
for (Slot *s = pSlot; s && s != end; s = s->nextSibling())
{
CharInfo *c = charinfo(s->before());
if (isWhitespace(c->unicodeChar()))
{
s->setJustify(this, 0, 3, 1);
s->setJustify(this, 0, 2, 1);
s->setJustify(this, 0, 0, -1);
++icount;
}
}
if (!icount)
{
for (Slot *s = pSlot; s && s != end; s = s->nextSibling())
{
s->setJustify(this, 0, 3, 1);
s->setJustify(this, 0, 2, 1);
s->setJustify(this, 0, 0, -1);
}
}
++numLevels;
}
Vector<JustifyTotal> stats(numLevels);
for (Slot *s = pFirst; s && s != end; s = s->nextSibling())
{
float w = s->origin().x / scale + s->advance() - base;
if (w > currWidth) currWidth = w;
for (int j = 0; j < numLevels; ++j)
stats[j].accumulate(s, this, j);
s->just(0);
}
for (int i = (width < 0.0f) ? -1 : numLevels - 1; i >= 0; --i)
{
float diff;
float error = 0.;
float diffpw;
int tWeight = stats[i].weight();
if (tWeight == 0) continue;
do {
error = 0.;
diff = width - currWidth;
diffpw = diff / tWeight;
tWeight = 0;
for (Slot *s = pFirst; s && s != end; s = s->nextSibling()) // don't include final glyph
{
int w = s->getJustify(this, i, 3);
float pref = diffpw * w + error;
int step = s->getJustify(this, i, 2);
if (!step) step = 1; // handle lazy font developers
if (pref > 0)
{
float max = uint16(s->getJustify(this, i, 0));
if (i == 0) max -= s->just();
if (pref > max) pref = max;
else tWeight += w;
}
else
{
float max = uint16(s->getJustify(this, i, 1));
if (i == 0) max += s->just();
if (-pref > max) pref = -max;
else tWeight += w;
}
int actual = int(pref / step) * step;
if (actual)
{
error += diffpw * w - actual;
if (i == 0)
s->just(s->just() + actual);
else
s->setJustify(this, i, 4, actual);
}
}
currWidth += diff - error;
} while (i == 0 && int(std::abs(error)) > 0 && tWeight);
}
Slot *oldFirst = m_first;
Slot *oldLast = m_last;
if (silf()->flags() & 1)
{
m_first = pSlot = addLineEnd(pSlot);
m_last = pLast = addLineEnd(end);
if (!m_first || !m_last) return -1.0;
}
else
{
m_first = pSlot;
m_last = pLast;
}
// run justification passes here
#if !defined GRAPHITE2_NTRACING
json * const dbgout = m_face->logger();
if (dbgout)
*dbgout << json::object
<< "justifies" << objectid(this)
<< "passes" << json::array;
#endif
if (m_silf->justificationPass() != m_silf->positionPass() && (width >= 0.f || (silf()->flags() & 1)))
m_silf->runGraphite(this, m_silf->justificationPass(), m_silf->positionPass());
#if !defined GRAPHITE2_NTRACING
if (dbgout)
{
*dbgout << json::item << json::close; // Close up the passes array
positionSlots(NULL, pSlot, pLast, m_dir);
Slot *lEnd = pLast->nextSibling();
*dbgout << "output" << json::array;
for(Slot * t = pSlot; t != lEnd; t = t->next())
*dbgout << dslot(this, t);
*dbgout << json::close << json::close;
}
#endif
res = positionSlots(font, pSlot, pLast, m_dir);
if (silf()->flags() & 1)
{
if (m_first)
delLineEnd(m_first);
if (m_last)
delLineEnd(m_last);
}
m_first = oldFirst;
m_last = oldLast;
if ((m_dir & 1) != m_silf->dir() && m_silf->bidiPass() != m_silf->numPasses())
reverseSlots();
return res.x;
}
Slot *Segment::addLineEnd(Slot *nSlot)
{
Slot *eSlot = newSlot();
if (!eSlot) return NULL;
const uint16 gid = silf()->endLineGlyphid();
const GlyphFace * theGlyph = m_face->glyphs().glyphSafe(gid);
eSlot->setGlyph(this, gid, theGlyph);
if (nSlot)
{
eSlot->next(nSlot);
eSlot->prev(nSlot->prev());
nSlot->prev(eSlot);
eSlot->before(nSlot->before());
if (eSlot->prev())
eSlot->after(eSlot->prev()->after());
else
eSlot->after(nSlot->before());
}
else
{
nSlot = m_last;
eSlot->prev(nSlot);
nSlot->next(eSlot);
eSlot->after(eSlot->prev()->after());
eSlot->before(nSlot->after());
}
return eSlot;
}
void Segment::delLineEnd(Slot *s)
{
Slot *nSlot = s->next();
if (nSlot)
{
nSlot->prev(s->prev());
if (s->prev())
s->prev()->next(nSlot);
}
else
s->prev()->next(NULL);
freeSlot(s);
}