godot/thirdparty/graphite/src/Intervals.cpp

276 lines
7.4 KiB
C++

// SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later
// Copyright 2010, SIL International, All rights reserved.
#include <algorithm>
#include <cmath>
#include <limits>
#include "inc/Intervals.h"
#include "inc/Segment.h"
#include "inc/Slot.h"
#include "inc/debug.h"
#include "inc/bits.h"
using namespace graphite2;
#include <cmath>
inline
Zones::Exclusion Zones::Exclusion::split_at(float p) {
Exclusion r(*this);
r.xm = x = p;
return r;
}
inline
void Zones::Exclusion::left_trim(float p) {
x = p;
}
inline
Zones::Exclusion & Zones::Exclusion::operator += (Exclusion const & rhs) {
c += rhs.c; sm += rhs.sm; smx += rhs.smx; open = false;
return *this;
}
inline
uint8 Zones::Exclusion::outcode(float val) const {
float p = val;
//float d = std::numeric_limits<float>::epsilon();
float d = 0.;
return ((p - xm >= d) << 1) | (x - p > d);
}
void Zones::exclude_with_margins(float xmin, float xmax, int axis) {
remove(xmin, xmax);
weightedAxis(axis, xmin-_margin_len, xmin, 0, 0, _margin_weight, xmin-_margin_len, 0, 0, false);
weightedAxis(axis, xmax, xmax+_margin_len, 0, 0, _margin_weight, xmax+_margin_len, 0, 0, false);
}
namespace
{
inline
bool separated(float a, float b) {
return a != b;
//int exp;
//float res = frexpf(fabs(a - b), &exp);
//return (*(unsigned int *)(&res) > 4);
//return std::fabs(a-b) > std::numeric_limits<float>::epsilon(); // std::epsilon may not work. but 0.5 fails exising 64 bit tests
//return std::fabs(a-b) > 0.5f;
}
}
void Zones::insert(Exclusion e)
{
#if !defined GRAPHITE2_NTRACING
addDebug(&e);
#endif
e.x = max(e.x, _pos);
e.xm = min(e.xm, _posm);
if (e.x >= e.xm) return;
for (iterator i = _exclusions.begin(), ie = _exclusions.end(); i != ie && e.x < e.xm; ++i)
{
const uint8 oca = e.outcode(i->x),
ocb = e.outcode(i->xm);
if ((oca & ocb) != 0) continue;
switch (oca ^ ocb) // What kind of overlap?
{
case 0: // e completely covers i
// split e at i.x into e1,e2
// split e2 at i.mx into e2,e3
// drop e1 ,i+e2, e=e3
*i += e;
e.left_trim(i->xm);
break;
case 1: // e overlaps on the rhs of i
// split i at e->x into i1,i2
// split e at i.mx into e1,e2
// trim i1, insert i2+e1, e=e2
if (!separated(i->xm, e.x)) break;
if (separated(i->x,e.x)) { i = _exclusions.insert(i,i->split_at(e.x)); ++i; }
*i += e;
e.left_trim(i->xm);
break;
case 2: // e overlaps on the lhs of i
// split e at i->x into e1,e2
// split i at e.mx into i1,i2
// drop e1, insert e2+i1, trim i2
if (!separated(e.xm, i->x)) return;
if (separated(e.xm, i->xm)) i = _exclusions.insert(i,i->split_at(e.xm));
*i += e;
return;
case 3: // i completely covers e
// split i at e.x into i1,i2
// split i2 at e.mx into i2,i3
// insert i1, insert e+i2
if (separated(e.xm, i->xm)) i = _exclusions.insert(i,i->split_at(e.xm));
i = _exclusions.insert(i, i->split_at(e.x));
*++i += e;
return;
}
ie = _exclusions.end();
}
}
void Zones::remove(float x, float xm)
{
#if !defined GRAPHITE2_NTRACING
removeDebug(x, xm);
#endif
x = max(x, _pos);
xm = min(xm, _posm);
if (x >= xm) return;
for (iterator i = _exclusions.begin(), ie = _exclusions.end(); i != ie; ++i)
{
const uint8 oca = i->outcode(x),
ocb = i->outcode(xm);
if ((oca & ocb) != 0) continue;
switch (oca ^ ocb) // What kind of overlap?
{
case 0: // i completely covers e
if (separated(i->x, x)) { i = _exclusions.insert(i,i->split_at(x)); ++i; }
GR_FALLTHROUGH;
// no break
case 1: // i overlaps on the rhs of e
i->left_trim(xm);
return;
case 2: // i overlaps on the lhs of e
i->xm = x;
if (separated(i->x, i->xm)) break;
GR_FALLTHROUGH;
// no break
case 3: // e completely covers i
i = _exclusions.erase(i);
--i;
break;
}
ie = _exclusions.end();
}
}
Zones::const_iterator Zones::find_exclusion_under(float x) const
{
size_t l = 0, h = _exclusions.size();
while (l < h)
{
size_t const p = (l+h) >> 1;
switch (_exclusions[p].outcode(x))
{
case 0 : return _exclusions.begin()+p;
case 1 : h = p; break;
case 2 :
case 3 : l = p+1; break;
}
}
return _exclusions.begin()+l;
}
float Zones::closest(float origin, float & cost) const
{
float best_c = std::numeric_limits<float>::max(),
best_x = 0;
const const_iterator start = find_exclusion_under(origin);
// Forward scan looking for lowest cost
for (const_iterator i = start, ie = _exclusions.end(); i != ie; ++i)
if (i->track_cost(best_c, best_x, origin)) break;
// Backward scan looking for lowest cost
// We start from the exclusion to the immediate left of start since we've
// already tested start with the right most scan above.
for (const_iterator i = start-1, ie = _exclusions.begin()-1; i != ie; --i)
if (i->track_cost(best_c, best_x, origin)) break;
cost = (best_c == std::numeric_limits<float>::max() ? -1 : best_c);
return best_x;
}
// Cost and test position functions
bool Zones::Exclusion::track_cost(float & best_cost, float & best_pos, float origin) const {
const float p = test_position(origin),
localc = cost(p - origin);
if (open && localc > best_cost) return true;
if (localc < best_cost)
{
best_cost = localc;
best_pos = p;
}
return false;
}
inline
float Zones::Exclusion::cost(float p) const {
return (sm * p - 2 * smx) * p + c;
}
float Zones::Exclusion::test_position(float origin) const {
if (sm < 0)
{
// sigh, test both ends and perhaps the middle too!
float res = x;
float cl = cost(x);
if (x < origin && xm > origin)
{
float co = cost(origin);
if (co < cl)
{
cl = co;
res = origin;
}
}
float cr = cost(xm);
return cl > cr ? xm : res;
}
else
{
float zerox = smx / sm + origin;
if (zerox < x) return x;
else if (zerox > xm) return xm;
else return zerox;
}
}
#if !defined GRAPHITE2_NTRACING
void Zones::jsonDbgOut(Segment *seg) const {
if (_dbg)
{
for (Zones::idebugs s = dbgs_begin(), e = dbgs_end(); s != e; ++s)
{
*_dbg << json::flat << json::array
<< objectid(dslot(seg, (Slot *)(s->_env[0])))
<< reinterpret_cast<ptrdiff_t>(s->_env[1]);
if (s->_isdel)
*_dbg << "remove" << Position(s->_excl.x, s->_excl.xm);
else
*_dbg << "exclude" << json::flat << json::array
<< s->_excl.x << s->_excl.xm
<< s->_excl.sm << s->_excl.smx << s->_excl.c
<< json::close;
*_dbg << json::close;
}
}
}
#endif