Source code

Revision control

Copy as Markdown

Other Tools

/* GRAPHITE2 LICENSING
Copyright 2010, SIL International
All rights reserved.
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should also have received a copy of the GNU Lesser General Public
License along with this library in the file named "LICENSE".
If not, write to the Free Software Foundation, 51 Franklin Street,
Suite 500, Boston, MA 02110-1335, USA or visit their web page on the
Alternatively, the contents of this file may be used under the terms of the
Mozilla Public License (http://mozilla.org/MPL) or the GNU General Public
License, as published by the Free Software Foundation, either version 2
of the License or (at your option) any later version.
*/
#include <algorithm>
#include <limits>
#include <cmath>
#include <string>
#include <functional>
#include "inc/Collider.h"
#include "inc/Segment.h"
#include "inc/Slot.h"
#include "inc/GlyphCache.h"
#include "inc/Sparse.h"
#define ISQRT2 0.707106781f
// Possible rounding error for subbox boundaries: 0.016 = 1/64 = 1/256 * 4
// (values in font range from 0..256)
// #define SUBBOX_RND_ERR 0.016
using namespace graphite2;
//// SHIFT-COLLIDER ////
// Initialize the Collider to hold the basic movement limits for the
// target slot, the one we are focusing on fixing.
bool ShiftCollider::initSlot(Segment *seg, Slot *aSlot, const Rect &limit, float margin, float marginWeight,
const Position &currShift, const Position &currOffset, int dir, GR_MAYBE_UNUSED json * const dbgout)
{
int i;
float mx, mn;
float a, shift;
const GlyphCache &gc = seg->getFace()->glyphs();
unsigned short gid = aSlot->gid();
if (!gc.check(gid))
return false;
const BBox &bb = gc.getBoundingBBox(gid);
const SlantBox &sb = gc.getBoundingSlantBox(gid);
//float sx = aSlot->origin().x + currShift.x;
//float sy = aSlot->origin().y + currShift.y;
if (currOffset.x != 0.f || currOffset.y != 0.f)
_limit = Rect(limit.bl - currOffset, limit.tr - currOffset);
else
_limit = limit;
// For a ShiftCollider, these indices indicate which vector we are moving by:
// each _ranges represents absolute space with respect to the origin of the slot. Thus take into account true origins but subtract the vmin for the slot
for (i = 0; i < 4; ++i)
{
switch (i) {
case 0 : // x direction
mn = _limit.bl.x + currOffset.x;
mx = _limit.tr.x + currOffset.x;
_len[i] = bb.xa - bb.xi;
a = currOffset.y + currShift.y;
_ranges[i].initialise<XY>(mn, mx, margin, marginWeight, a);
break;
case 1 : // y direction
mn = _limit.bl.y + currOffset.y;
mx = _limit.tr.y + currOffset.y;
_len[i] = bb.ya - bb.yi;
a = currOffset.x + currShift.x;
_ranges[i].initialise<XY>(mn, mx, margin, marginWeight, a);
break;
case 2 : // sum (negatively sloped diagonal boundaries)
// pick closest x,y limit boundaries in s direction
shift = currOffset.x + currOffset.y + currShift.x + currShift.y;
mn = -2 * min(currShift.x - _limit.bl.x, currShift.y - _limit.bl.y) + shift;
mx = 2 * min(_limit.tr.x - currShift.x, _limit.tr.y - currShift.y) + shift;
_len[i] = sb.sa - sb.si;
a = currOffset.x - currOffset.y + currShift.x - currShift.y;
_ranges[i].initialise<SD>(mn, mx, margin / ISQRT2, marginWeight, a);
break;
case 3 : // diff (positively sloped diagonal boundaries)
// pick closest x,y limit boundaries in d direction
shift = currOffset.x - currOffset.y + currShift.x - currShift.y;
mn = -2 * min(currShift.x - _limit.bl.x, _limit.tr.y - currShift.y) + shift;
mx = 2 * min(_limit.tr.x - currShift.x, currShift.y - _limit.bl.y) + shift;
_len[i] = sb.da - sb.di;
a = currOffset.x + currOffset.y + currShift.x + currShift.y;
_ranges[i].initialise<SD>(mn, mx, margin / ISQRT2, marginWeight, a);
break;
}
}
_target = aSlot;
if ((dir & 1) == 0)
{
// For LTR, switch and negate x limits.
_limit.bl.x = -1 * limit.tr.x;
//_limit.tr.x = -1 * limit.bl.x;
}
_currOffset = currOffset;
_currShift = currShift;
_origin = aSlot->origin() - currOffset; // the original anchor position of the glyph
_margin = margin;
_marginWt = marginWeight;
SlotCollision *c = seg->collisionInfo(aSlot);
_seqClass = c->seqClass();
_seqProxClass = c->seqProxClass();
_seqOrder = c->seqOrder();
return true;
}
template <class O>
float sdm(float vi, float va, float mx, float my, O op)
{
float res = 2 * mx - vi;
if (op(res, vi + 2 * my))
{
res = va + 2 * my;
if (op(res, 2 * mx - va))
res = mx + my;
}
return res;
}
// Mark an area with a cost that can vary along the x or y axis. The region is expressed in terms of the centre of the target glyph in each axis
void ShiftCollider::addBox_slope(bool isx, const Rect &box, const BBox &bb, const SlantBox &sb, const Position &org, float weight, float m, bool minright, int axis)
{
float a, c;
switch (axis) {
case 0 :
if (box.bl.y < org.y + bb.ya && box.tr.y > org.y + bb.yi && box.width() > 0)
{
a = org.y + 0.5f * (bb.yi + bb.ya);
c = 0.5f * (bb.xi + bb.xa);
if (isx)
_ranges[axis].weighted<XY>(box.bl.x - c, box.tr.x - c, weight, a, m,
(minright ? box.tr.x : box.bl.x) - c, a, 0, false);
else
_ranges[axis].weighted<XY>(box.bl.x - c, box.tr.x - c, weight, a, 0, 0, org.y,
m * (a * a + sqr((minright ? box.tr.y : box.bl.y) - 0.5f * (bb.yi + bb.ya))), false);
}
break;
case 1 :
if (box.bl.x < org.x + bb.xa && box.tr.x > org.x + bb.xi && box.height() > 0)
{
a = org.x + 0.5f * (bb.xi + bb.xa);
c = 0.5f * (bb.yi + bb.ya);
if (isx)
_ranges[axis].weighted<XY>(box.bl.y - c, box.tr.y - c, weight, a, 0, 0, org.x,
m * (a * a + sqr((minright ? box.tr.x : box.bl.x) - 0.5f * (bb.xi + bb.xa))), false);
else
_ranges[axis].weighted<XY>(box.bl.y - c, box.tr.y - c, weight, a, m,
(minright ? box.tr.y : box.bl.y) - c, a, 0, false);
}
break;
case 2 :
if (box.bl.x - box.tr.y < org.x - org.y + sb.da && box.tr.x - box.bl.y > org.x - org.y + sb.di)
{
float d = org.x - org.y + 0.5f * (sb.di + sb.da);
c = 0.5f * (sb.si + sb.sa);
float smax = min(2 * box.tr.x - d, 2 * box.tr.y + d);
float smin = max(2 * box.bl.x - d, 2 * box.bl.y + d);
if (smin > smax) return;
float si;
a = d;
if (isx)
si = 2 * (minright ? box.tr.x : box.bl.x) - a;
else
si = 2 * (minright ? box.tr.y : box.bl.y) + a;
_ranges[axis].weighted<SD>(smin - c, smax - c, weight / 2, a, m / 2, si, 0, 0, isx);
}
break;
case 3 :
if (box.bl.x + box.bl.y < org.x + org.y + sb.sa && box.tr.x + box.tr.y > org.x + org.y + sb.si)
{
float s = org.x + org.y + 0.5f * (sb.si + sb.sa);
c = 0.5f * (sb.di + sb.da);
float dmax = min(2 * box.tr.x - s, s - 2 * box.bl.y);
float dmin = max(2 * box.bl.x - s, s - 2 * box.tr.y);
if (dmin > dmax) return;
float di;
a = s;
if (isx)
di = 2 * (minright ? box.tr.x : box.bl.x) - a;
else
di = 2 * (minright ? box.tr.y : box.bl.y) + a;
_ranges[axis].weighted<SD>(dmin - c, dmax - c, weight / 2, a, m / 2, di, 0, 0, !isx);
}
break;
default :
break;
}
return;
}
// Mark an area with an absolute cost, making it completely inaccessible.
inline void ShiftCollider::removeBox(const Rect &box, const BBox &bb, const SlantBox &sb, const Position &org, int axis)
{
float c;
switch (axis) {
case 0 :
if (box.bl.y < org.y + bb.ya && box.tr.y > org.y + bb.yi && box.width() > 0)
{
c = 0.5f * (bb.xi + bb.xa);
_ranges[axis].exclude(box.bl.x - c, box.tr.x - c);
}
break;
case 1 :
if (box.bl.x < org.x + bb.xa && box.tr.x > org.x + bb.xi && box.height() > 0)
{
c = 0.5f * (bb.yi + bb.ya);
_ranges[axis].exclude(box.bl.y - c, box.tr.y - c);
}
break;
case 2 :
if (box.bl.x - box.tr.y < org.x - org.y + sb.da && box.tr.x - box.bl.y > org.x - org.y + sb.di
&& box.width() > 0 && box.height() > 0)
{
float di = org.x - org.y + sb.di;
float da = org.x - org.y + sb.da;
float smax = sdm(di, da, box.tr.x, box.tr.y, std::greater<float>());
float smin = sdm(da, di, box.bl.x, box.bl.y, std::less<float>());
c = 0.5f * (sb.si + sb.sa);
_ranges[axis].exclude(smin - c, smax - c);
}
break;
case 3 :
if (box.bl.x + box.bl.y < org.x + org.y + sb.sa && box.tr.x + box.tr.y > org.x + org.y + sb.si
&& box.width() > 0 && box.height() > 0)
{
float si = org.x + org.y + sb.si;
float sa = org.x + org.y + sb.sa;
float dmax = sdm(si, sa, box.tr.x, -box.bl.y, std::greater<float>());
float dmin = sdm(sa, si, box.bl.x, -box.tr.y, std::less<float>());
c = 0.5f * (sb.di + sb.da);
_ranges[axis].exclude(dmin - c, dmax - c);
}
break;
default :
break;
}
return;
}
// Adjust the movement limits for the target to avoid having it collide
// with the given neighbor slot. Also determine if there is in fact a collision
// between the target and the given slot.
bool ShiftCollider::mergeSlot(Segment *seg, Slot *slot, const SlotCollision *cslot, const Position &currShift,
bool isAfter, // slot is logically after _target
bool sameCluster, bool &hasCol, bool isExclusion,
GR_MAYBE_UNUSED json * const dbgout )
{
bool isCol = false;
const float sx = slot->origin().x - _origin.x + currShift.x;
const float sy = slot->origin().y - _origin.y + currShift.y;
const float sd = sx - sy;
const float ss = sx + sy;
float vmin, vmax;
float omin, omax, otmin, otmax;
float cmin, cmax; // target limits
float torg;
const GlyphCache &gc = seg->getFace()->glyphs();
const unsigned short gid = slot->gid();
if (!gc.check(gid))
return false;
const BBox &bb = gc.getBoundingBBox(gid);
// SlotCollision * cslot = seg->collisionInfo(slot);
int orderFlags = 0;
bool sameClass = _seqProxClass == 0 && cslot->seqClass() == _seqClass;
if (sameCluster && _seqClass
&& (sameClass || (_seqProxClass != 0 && cslot->seqClass() == _seqProxClass)))
// Force the target glyph to be in the specified direction from the slot we're testing.
orderFlags = _seqOrder;
// short circuit if only interested in direct collision and we are out of range
if (orderFlags || (sx + bb.xa + _margin >= _limit.bl.x && sx + bb.xi - _margin <= _limit.tr.x)
|| (sy + bb.ya + _margin >= _limit.bl.y && sy + bb.yi - _margin <= _limit.tr.y))
{
const float tx = _currOffset.x + _currShift.x;
const float ty = _currOffset.y + _currShift.y;
const float td = tx - ty;
const float ts = tx + ty;
const SlantBox &sb = gc.getBoundingSlantBox(gid);
const unsigned short tgid = _target->gid();
const BBox &tbb = gc.getBoundingBBox(tgid);
const SlantBox &tsb = gc.getBoundingSlantBox(tgid);
float seq_above_wt = cslot->seqAboveWt();
float seq_below_wt = cslot->seqBelowWt();
float seq_valign_wt = cslot->seqValignWt();
float lmargin;
// if isAfter, invert orderFlags for diagonal orders.
if (isAfter)
{
// invert appropriate bits
orderFlags ^= (sameClass ? 0x3F : 0x3);
// consider 2 bits at a time, non overlapping. If both bits set, clear them
orderFlags = orderFlags ^ ((((orderFlags >> 1) & orderFlags) & 0x15) * 3);
}
#if !defined GRAPHITE2_NTRACING
if (dbgout)
dbgout->setenv(0, slot);
#endif
// Process main bounding octabox.
for (int i = 0; i < 4; ++i)
{
switch (i) {
case 0 : // x direction
vmin = max(max(bb.xi - tbb.xa + sx, sb.di - tsb.da + ty + sd), sb.si - tsb.sa - ty + ss);
vmax = min(min(bb.xa - tbb.xi + sx, sb.da - tsb.di + ty + sd), sb.sa - tsb.si - ty + ss);
otmin = tbb.yi + ty;
otmax = tbb.ya + ty;
omin = bb.yi + sy;
omax = bb.ya + sy;
torg = _currOffset.x;
cmin = _limit.bl.x + torg;
cmax = _limit.tr.x - tbb.xi + tbb.xa + torg;
lmargin = _margin;
break;
case 1 : // y direction
vmin = max(max(bb.yi - tbb.ya + sy, tsb.di - sb.da + tx - sd), sb.si - tsb.sa - tx + ss);
vmax = min(min(bb.ya - tbb.yi + sy, tsb.da - sb.di + tx - sd), sb.sa - tsb.si - tx + ss);
otmin = tbb.xi + tx;
otmax = tbb.xa + tx;
omin = bb.xi + sx;
omax = bb.xa + sx;
torg = _currOffset.y;
cmin = _limit.bl.y + torg;
cmax = _limit.tr.y - tbb.yi + tbb.ya + torg;
lmargin = _margin;
break;
case 2 : // sum - moving along the positively-sloped vector, so the boundaries are the
// negatively-sloped boundaries.
vmin = max(max(sb.si - tsb.sa + ss, 2 * (bb.yi - tbb.ya + sy) + td), 2 * (bb.xi - tbb.xa + sx) - td);
vmax = min(min(sb.sa - tsb.si + ss, 2 * (bb.ya - tbb.yi + sy) + td), 2 * (bb.xa - tbb.xi + sx) - td);
otmin = tsb.di + td;
otmax = tsb.da + td;
omin = sb.di + sd;
omax = sb.da + sd;
torg = _currOffset.x + _currOffset.y;
cmin = _limit.bl.x + _limit.bl.y + torg;
cmax = _limit.tr.x + _limit.tr.y - tsb.si + tsb.sa + torg;
lmargin = _margin / ISQRT2;
break;
case 3 : // diff - moving along the negatively-sloped vector, so the boundaries are the
// positively-sloped boundaries.
vmin = max(max(sb.di - tsb.da + sd, 2 * (bb.xi - tbb.xa + sx) - ts), -2 * (bb.ya - tbb.yi + sy) + ts);
vmax = min(min(sb.da - tsb.di + sd, 2 * (bb.xa - tbb.xi + sx) - ts), -2 * (bb.yi - tbb.ya + sy) + ts);
otmin = tsb.si + ts;
otmax = tsb.sa + ts;
omin = sb.si + ss;
omax = sb.sa + ss;
torg = _currOffset.x - _currOffset.y;
cmin = _limit.bl.x - _limit.tr.y + torg;
cmax = _limit.tr.x - _limit.bl.y - tsb.di + tsb.da + torg;
lmargin = _margin / ISQRT2;
break;
default :
continue;
}
#if !defined GRAPHITE2_NTRACING
if (dbgout)
dbgout->setenv(1, reinterpret_cast<void *>(-1));
#define DBGTAG(x) if (dbgout) dbgout->setenv(1, reinterpret_cast<void *>(-x));
#else
#define DBGTAG(x)
#endif
if (orderFlags)
{
Position org(tx, ty);
float xminf = _limit.bl.x + _currOffset.x + tbb.xi;
float xpinf = _limit.tr.x + _currOffset.x + tbb.xa;
float ypinf = _limit.tr.y + _currOffset.y + tbb.ya;
float yminf = _limit.bl.y + _currOffset.y + tbb.yi;
switch (orderFlags) {
case SlotCollision::SEQ_ORDER_RIGHTUP :
{
float r1Xedge = cslot->seqAboveXoff() + 0.5f * (bb.xi + bb.xa) + sx;
float r3Xedge = cslot->seqBelowXlim() + bb.xa + sx + 0.5f * (tbb.xa - tbb.xi);
float r2Yedge = 0.5f * (bb.yi + bb.ya) + sy;
// DBGTAG(1x) means the regions are up and right
// region 1
DBGTAG(11)
addBox_slope(true, Rect(Position(xminf, r2Yedge), Position(r1Xedge, ypinf)),
tbb, tsb, org, 0, seq_above_wt, true, i);
// region 2
DBGTAG(12)
removeBox(Rect(Position(xminf, yminf), Position(r3Xedge, r2Yedge)), tbb, tsb, org, i);
// region 3, which end is zero is irrelevant since m weight is 0
DBGTAG(13)
addBox_slope(true, Rect(Position(r3Xedge, yminf), Position(xpinf, r2Yedge - cslot->seqValignHt())),
tbb, tsb, org, seq_below_wt, 0, true, i);
// region 4
DBGTAG(14)
addBox_slope(false, Rect(Position(sx + bb.xi, r2Yedge), Position(xpinf, r2Yedge + cslot->seqValignHt())),
tbb, tsb, org, 0, seq_valign_wt, true, i);
// region 5
DBGTAG(15)
addBox_slope(false, Rect(Position(sx + bb.xi, r2Yedge - cslot->seqValignHt()), Position(xpinf, r2Yedge)),
tbb, tsb, org, seq_below_wt, seq_valign_wt, false, i);
break;
}
case SlotCollision::SEQ_ORDER_LEFTDOWN :
{
float r1Xedge = 0.5f * (bb.xi + bb.xa) + cslot->seqAboveXoff() + sx;
float r3Xedge = bb.xi - cslot->seqBelowXlim() + sx - 0.5f * (tbb.xa - tbb.xi);
float r2Yedge = 0.5f * (bb.yi + bb.ya) + sy;
// DBGTAG(2x) means the regions are up and right
// region 1
DBGTAG(21)
addBox_slope(true, Rect(Position(r1Xedge, yminf), Position(xpinf, r2Yedge)),
tbb, tsb, org, 0, seq_above_wt, false, i);
// region 2
DBGTAG(22)
removeBox(Rect(Position(r3Xedge, r2Yedge), Position(xpinf, ypinf)), tbb, tsb, org, i);
// region 3
DBGTAG(23)
addBox_slope(true, Rect(Position(xminf, r2Yedge - cslot->seqValignHt()), Position(r3Xedge, ypinf)),
tbb, tsb, org, seq_below_wt, 0, false, i);
// region 4
DBGTAG(24)
addBox_slope(false, Rect(Position(xminf, r2Yedge), Position(sx + bb.xa, r2Yedge + cslot->seqValignHt())),
tbb, tsb, org, 0, seq_valign_wt, true, i);
// region 5
DBGTAG(25)
addBox_slope(false, Rect(Position(xminf, r2Yedge - cslot->seqValignHt()),
Position(sx + bb.xa, r2Yedge)), tbb, tsb, org, seq_below_wt, seq_valign_wt, false, i);
break;
}
case SlotCollision::SEQ_ORDER_NOABOVE : // enforce neighboring glyph being above
DBGTAG(31);
removeBox(Rect(Position(bb.xi - tbb.xa + sx, sy + bb.ya),
Position(bb.xa - tbb.xi + sx, ypinf)), tbb, tsb, org, i);
break;
case SlotCollision::SEQ_ORDER_NOBELOW : // enforce neighboring glyph being below
DBGTAG(32);
removeBox(Rect(Position(bb.xi - tbb.xa + sx, yminf),
Position(bb.xa - tbb.xi + sx, sy + bb.yi)), tbb, tsb, org, i);
break;
case SlotCollision::SEQ_ORDER_NOLEFT : // enforce neighboring glyph being to the left
DBGTAG(33)
removeBox(Rect(Position(xminf, bb.yi - tbb.ya + sy),
Position(bb.xi - tbb.xa + sx, bb.ya - tbb.yi + sy)), tbb, tsb, org, i);
break;
case SlotCollision::SEQ_ORDER_NORIGHT : // enforce neighboring glyph being to the right
DBGTAG(34)
removeBox(Rect(Position(bb.xa - tbb.xi + sx, bb.yi - tbb.ya + sy),
Position(xpinf, bb.ya - tbb.yi + sy)), tbb, tsb, org, i);
break;
default :
break;
}
}
if (vmax < cmin - lmargin || vmin > cmax + lmargin || omax < otmin - lmargin || omin > otmax + lmargin)
continue;
// Process sub-boxes that are defined for this glyph.
// We only need to do this if there was in fact a collision with the main octabox.
uint8 numsub = gc.numSubBounds(gid);
if (numsub > 0)
{
bool anyhits = false;
for (int j = 0; j < numsub; ++j)
{
const BBox &sbb = gc.getSubBoundingBBox(gid, j);
const SlantBox &ssb = gc.getSubBoundingSlantBox(gid, j);
switch (i) {
case 0 : // x
vmin = max(max(sbb.xi-tbb.xa+sx, ssb.di-tsb.da+sd+ty), ssb.si-tsb.sa+ss-ty);
vmax = min(min(sbb.xa-tbb.xi+sx, ssb.da-tsb.di+sd+ty), ssb.sa-tsb.si+ss-ty);
omin = sbb.yi + sy;
omax = sbb.ya + sy;
break;
case 1 : // y
vmin = max(max(sbb.yi-tbb.ya+sy, tsb.di-ssb.da-sd+tx), ssb.si-tsb.sa+ss-tx);
vmax = min(min(sbb.ya-tbb.yi+sy, tsb.da-ssb.di-sd+tx), ssb.sa-tsb.si+ss-tx);
omin = sbb.xi + sx;
omax = sbb.xa + sx;
break;
case 2 : // sum
vmin = max(max(ssb.si-tsb.sa+ss, 2*(sbb.yi-tbb.ya+sy)+td), 2*(sbb.xi-tbb.xa+sx)-td);
vmax = min(min(ssb.sa-tsb.si+ss, 2*(sbb.ya-tbb.yi+sy)+td), 2*(sbb.xa-tbb.xi+sx)-td);
omin = ssb.di + sd;
omax = ssb.da + sd;
break;
case 3 : // diff
vmin = max(max(ssb.di-tsb.da+sd, 2*(sbb.xi-tbb.xa+sx)-ts), -2*(sbb.ya-tbb.yi+sy)+ts);
vmax = min(min(ssb.da-tsb.di+sd, 2*(sbb.xa-tbb.xi+sx)-ts), -2*(sbb.yi-tbb.ya+sy)+ts);
omin = ssb.si + ss;
omax = ssb.sa + ss;
break;
}
if (vmax < cmin - lmargin || vmin > cmax + lmargin || omax < otmin - lmargin || omin > otmax + lmargin)
continue;
#if !defined GRAPHITE2_NTRACING
if (dbgout)
dbgout->setenv(1, reinterpret_cast<void *>(j));
#endif
if (omin > otmax)
_ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0,
sqr(lmargin - omin + otmax) * _marginWt, false);
else if (omax < otmin)
_ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0,
sqr(lmargin - otmin + omax) * _marginWt, false);
else
_ranges[i].exclude_with_margins(vmin, vmax, i);
anyhits = true;
}
if (anyhits)
isCol = true;
}
else // no sub-boxes
{
#if !defined GRAPHITE2_NTRACING
if (dbgout)
dbgout->setenv(1, reinterpret_cast<void *>(-1));
#endif
isCol = true;
if (omin > otmax)
_ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0,
sqr(lmargin - omin + otmax) * _marginWt, false);
else if (omax < otmin)
_ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0,
sqr(lmargin - otmin + omax) * _marginWt, false);
else
_ranges[i].exclude_with_margins(vmin, vmax, i);
}
}
}
bool res = true;
if (cslot->exclGlyph() > 0 && gc.check(cslot->exclGlyph()) && !isExclusion)
{
// Set up the bogus slot representing the exclusion glyph.
Slot *exclSlot = seg->newSlot();
if (!exclSlot)
return res;
exclSlot->setGlyph(seg, cslot->exclGlyph());
Position exclOrigin(slot->origin() + cslot->exclOffset());
exclSlot->origin(exclOrigin);
SlotCollision exclInfo(seg, exclSlot);
res &= mergeSlot(seg, exclSlot, &exclInfo, currShift, isAfter, sameCluster, isCol, true, dbgout );
seg->freeSlot(exclSlot);
}
hasCol |= isCol;
return res;
} // end of ShiftCollider::mergeSlot
// Figure out where to move the target glyph to, and return the amount to shift by.
Position ShiftCollider::resolve(GR_MAYBE_UNUSED Segment *seg, bool &isCol, GR_MAYBE_UNUSED json * const dbgout)
{
float tbase;
float totalCost = (float)(std::numeric_limits<float>::max() / 2);
Position resultPos = Position(0, 0);
#if !defined GRAPHITE2_NTRACING
int bestAxis = -1;
if (dbgout)
{
outputJsonDbgStartSlot(dbgout, seg);
*dbgout << "vectors" << json::array;
}
#endif
isCol = true;
for (int i = 0; i < 4; ++i)
{
float bestCost = -1;
float bestPos;
// Calculate the margin depending on whether we are moving diagonally or not:
switch (i) {
case 0 : // x direction
tbase = _currOffset.x;
break;
case 1 : // y direction
tbase = _currOffset.y;
break;
case 2 : // sum (negatively-sloped diagonals)
tbase = _currOffset.x + _currOffset.y;
break;
case 3 : // diff (positively-sloped diagonals)
tbase = _currOffset.x - _currOffset.y;
break;
}
Position testp;
bestPos = _ranges[i].closest(0, bestCost) - tbase; // Get the best relative position
#if !defined GRAPHITE2_NTRACING
if (dbgout)
outputJsonDbgOneVector(dbgout, seg, i, tbase, bestCost, bestPos) ;
#endif
if (bestCost >= 0.0f)
{
isCol = false;
switch (i) {
case 0 : testp = Position(bestPos, _currShift.y); break;
case 1 : testp = Position(_currShift.x, bestPos); break;
case 2 : testp = Position(0.5f * (_currShift.x - _currShift.y + bestPos), 0.5f * (_currShift.y - _currShift.x + bestPos)); break;
case 3 : testp = Position(0.5f * (_currShift.x + _currShift.y + bestPos), 0.5f * (_currShift.x + _currShift.y - bestPos)); break;
}
if (bestCost < totalCost - 0.01f)
{
totalCost = bestCost;
resultPos = testp;
#if !defined GRAPHITE2_NTRACING
bestAxis = i;
#endif
}
}
} // end of loop over 4 directions
#if !defined GRAPHITE2_NTRACING
if (dbgout)
outputJsonDbgEndSlot(dbgout, resultPos, bestAxis, isCol);
#endif
return resultPos;
} // end of ShiftCollider::resolve
#if !defined GRAPHITE2_NTRACING
void ShiftCollider::outputJsonDbg(json * const dbgout, Segment *seg, int axis)
{
int axisMax = axis;
if (axis < 0) // output all axes
{
*dbgout << "gid" << _target->gid()
<< "limit" << _limit
<< "target" << json::object
<< "origin" << _target->origin()
<< "margin" << _margin
<< "bbox" << seg->theGlyphBBoxTemporary(_target->gid())
<< "slantbox" << seg->getFace()->glyphs().slant(_target->gid())
<< json::close; // target object
*dbgout << "ranges" << json::array;
axis = 0;
axisMax = 3;
}
for (int iAxis = axis; iAxis <= axisMax; ++iAxis)
{
*dbgout << json::flat << json::array << _ranges[iAxis].position();
for (Zones::const_iterator s = _ranges[iAxis].begin(), e = _ranges[iAxis].end(); s != e; ++s)
*dbgout << json::flat << json::array
<< Position(s->x, s->xm) << s->sm << s->smx << s->c
<< json::close;
*dbgout << json::close;
}
if (axis < axisMax) // looped through the _ranges array for all axes
*dbgout << json::close; // ranges array
}
void ShiftCollider::outputJsonDbgStartSlot(json * const dbgout, Segment *seg)
{
*dbgout << json::object // slot - not closed till the end of the caller method
<< "slot" << objectid(dslot(seg, _target))
<< "gid" << _target->gid()
<< "limit" << _limit
<< "target" << json::object
<< "origin" << _origin
<< "currShift" << _currShift
<< "currOffset" << seg->collisionInfo(_target)->offset()
<< "bbox" << seg->theGlyphBBoxTemporary(_target->gid())
<< "slantBox" << seg->getFace()->glyphs().slant(_target->gid())
<< "fix" << "shift";
*dbgout << json::close; // target object
}
void ShiftCollider::outputJsonDbgEndSlot(GR_MAYBE_UNUSED json * const dbgout,
Position resultPos, int bestAxis, bool isCol)
{
*dbgout << json::close // vectors array
<< "result" << resultPos
//<< "scraping" << _scraping[bestAxis]
<< "bestAxis" << bestAxis
<< "stillBad" << isCol
<< json::close; // slot object
}
void ShiftCollider::outputJsonDbgOneVector(json * const dbgout, Segment *seg, int axis,
float tleft, float bestCost, float bestVal)
{
const char * label;
switch (axis)
{
case 0: label = "x"; break;
case 1: label = "y"; break;
case 2: label = "sum (NE-SW)"; break;
case 3: label = "diff (NW-SE)"; break;
default: label = "???"; break;
}
*dbgout << json::object // vector
<< "direction" << label
<< "targetMin" << tleft;
outputJsonDbgRemovals(dbgout, axis, seg);
*dbgout << "ranges";
outputJsonDbg(dbgout, seg, axis);
*dbgout << "bestCost" << bestCost
<< "bestVal" << bestVal + tleft
<< json::close; // vectors object
}
void ShiftCollider::outputJsonDbgRemovals(json * const dbgout, int axis, Segment *seg)
{
*dbgout << "removals" << json::array;
_ranges[axis].jsonDbgOut(seg);
*dbgout << json::close; // removals array
}
#endif // !defined GRAPHITE2_NTRACING
//// KERN-COLLIDER ////
inline
static float localmax (float al, float au, float bl, float bu, float x)
{
if (al < bl)
{ if (au < bu) return au < x ? au : x; }
else if (au > bu) return bl < x ? bl : x;
return x;
}
inline
static float localmin(float al, float au, float bl, float bu, float x)
{
if (bl > al)
{ if (bu > au) return bl > x ? bl : x; }
else if (au > bu) return al > x ? al : x;
return x;
}
// Return the given edge of the glyph at height y, taking any slant box into account.
static float get_edge(Segment *seg, const Slot *s, const Position &shift, float y, float width, float margin, bool isRight)
{
const GlyphCache &gc = seg->getFace()->glyphs();
unsigned short gid = s->gid();
float sx = s->origin().x + shift.x;
float sy = s->origin().y + shift.y;
uint8 numsub = gc.numSubBounds(gid);
float res = isRight ? (float)-1e38 : (float)1e38;
if (numsub > 0)
{
for (int i = 0; i < numsub; ++i)
{
const BBox &sbb = gc.getSubBoundingBBox(gid, i);
const SlantBox &ssb = gc.getSubBoundingSlantBox(gid, i);
if (sy + sbb.yi - margin > y + width / 2 || sy + sbb.ya + margin < y - width / 2)
continue;
if (isRight)
{
float x = sx + sbb.xa + margin;
if (x > res)
{
float td = sx - sy + ssb.da + margin + y;
float ts = sx + sy + ssb.sa + margin - y;
x = localmax(td - width / 2, td + width / 2, ts - width / 2, ts + width / 2, x);
if (x > res)
res = x;
}
}
else
{
float x = sx + sbb.xi - margin;
if (x < res)
{
float td = sx - sy + ssb.di - margin + y;
float ts = sx + sy + ssb.si - margin - y;
x = localmin(td - width / 2, td + width / 2, ts - width / 2, ts + width / 2, x);
if (x < res)
res = x;
}
}
}
}
else
{
const BBox &bb = gc.getBoundingBBox(gid);
const SlantBox &sb = gc.getBoundingSlantBox(gid);
if (sy + bb.yi - margin > y + width / 2 || sy + bb.ya + margin < y - width / 2)
return res;
float td = sx - sy + y;
float ts = sx + sy - y;
if (isRight)
res = localmax(td + sb.da - width / 2, td + sb.da + width / 2, ts + sb.sa - width / 2, ts + sb.sa + width / 2, sx + bb.xa) + margin;
else
res = localmin(td + sb.di - width / 2, td + sb.di + width / 2, ts + sb.si - width / 2, ts + sb.si + width / 2, sx + bb.xi) - margin;
}
return res;
}
bool KernCollider::initSlot(Segment *seg, Slot *aSlot, const Rect &limit, float margin,
const Position &currShift, const Position &offsetPrev, int dir,
float ymin, float ymax, GR_MAYBE_UNUSED json * const dbgout)
{
const GlyphCache &gc = seg->getFace()->glyphs();
const Slot *base = aSlot;
// const Slot *last = aSlot;
const Slot *s;
int numSlices;
while (base->attachedTo())
base = base->attachedTo();
if (margin < 10) margin = 10;
_limit = limit;
_offsetPrev = offsetPrev; // kern from a previous pass
// Calculate the height of the glyph and how many horizontal slices to use.
if (_maxy >= 1e37f)
{
_sliceWidth = margin / 1.5f;
_maxy = ymax + margin;
_miny = ymin - margin;
numSlices = int((_maxy - _miny + 2) / (_sliceWidth / 1.5f) + 1.f); // +2 helps with rounding errors
_edges.clear();
_edges.insert(_edges.begin(), numSlices, (dir & 1) ? 1e38f : -1e38f);
_xbound = (dir & 1) ? (float)1e38f : (float)-1e38f;
}
else if (_maxy != ymax || _miny != ymin)
{
if (_miny != ymin)
{
numSlices = int((ymin - margin - _miny) / _sliceWidth - 1);
_miny += numSlices * _sliceWidth;
if (numSlices < 0)
_edges.insert(_edges.begin(), -numSlices, (dir & 1) ? 1e38f : -1e38f);
else if ((unsigned)numSlices < _edges.size()) // this shouldn't fire since we always grow the range
{
Vector<float>::iterator e = _edges.begin();
while (numSlices--)
++e;
_edges.erase(_edges.begin(), e);
}
}
if (_maxy != ymax)
{
numSlices = int((ymax + margin - _miny) / _sliceWidth + 1);
_maxy = numSlices * _sliceWidth + _miny;
if (numSlices > (int)_edges.size())
_edges.insert(_edges.end(), numSlices - _edges.size(), (dir & 1) ? 1e38f : -1e38f);
else if (numSlices < (int)_edges.size()) // this shouldn't fire since we always grow the range
{
while ((int)_edges.size() > numSlices)
_edges.pop_back();
}
}
goto done;
}
numSlices = int(_edges.size());
#if !defined GRAPHITE2_NTRACING
// Debugging
_seg = seg;
_slotNear.clear();
_slotNear.insert(_slotNear.begin(), numSlices, NULL);
_nearEdges.clear();
_nearEdges.insert(_nearEdges.begin(), numSlices, (dir & 1) ? -1e38f : +1e38f);
#endif
// Determine the trailing edge of each slice (ie, left edge for a RTL glyph).
for (s = base; s; s = s->nextInCluster(s))
{
SlotCollision *c = seg->collisionInfo(s);
if (!gc.check(s->gid()))
return false;
const BBox &bs = gc.getBoundingBBox(s->gid());
float x = s->origin().x + c->shift().x + ((dir & 1) ? bs.xi : bs.xa);
// Loop over slices.
// Note smin might not be zero if glyph s is not at the bottom of the cluster; similarly for smax.
float toffset = c->shift().y - _miny + 1 + s->origin().y;
int smin = max(0, int((bs.yi + toffset) / _sliceWidth));
int smax = min(numSlices - 1, int((bs.ya + toffset) / _sliceWidth + 1));
for (int i = smin; i <= smax; ++i)
{
float t;
float y = _miny - 1 + (i + .5f) * _sliceWidth; // vertical center of slice
if ((dir & 1) && x < _edges[i])
{
t = get_edge(seg, s, c->shift(), y, _sliceWidth, margin, false);
if (t < _edges[i])
{
_edges[i] = t;
if (t < _xbound)
_xbound = t;
}
}
else if (!(dir & 1) && x > _edges[i])
{
t = get_edge(seg, s, c->shift(), y, _sliceWidth, margin, true);
if (t > _edges[i])
{
_edges[i] = t;
if (t > _xbound)
_xbound = t;
}
}
}
}
done:
_mingap = (float)1e37; // less than 1e38 s.t. 1e38-_mingap is really big
_target = aSlot;
_margin = margin;
_currShift = currShift;
return true;
} // end of KernCollider::initSlot
// Determine how much the target slot needs to kern away from the given slot.
// In other words, merge information from given slot's position with what the target slot knows
// about how it can kern.
// Return false if we know there is no collision, true if we think there might be one.
bool KernCollider::mergeSlot(Segment *seg, Slot *slot, const Position &currShift, float currSpace, int dir, GR_MAYBE_UNUSED json * const dbgout)
{
int rtl = (dir & 1) * 2 - 1;
if (!seg->getFace()->glyphs().check(slot->gid()))
return false;
const Rect &bb = seg->theGlyphBBoxTemporary(slot->gid());
const float sx = slot->origin().x + currShift.x;
float x = (sx + (rtl > 0 ? bb.tr.x : bb.bl.x)) * rtl;
// this isn't going to reduce _mingap so skip
if (_hit && x < rtl * (_xbound - _mingap - currSpace))
return false;
const float sy = slot->origin().y + currShift.y;
int smin = max(1, int((bb.bl.y + (1 - _miny + sy)) / _sliceWidth + 1)) - 1;
int smax = min((int)_edges.size() - 2, int((bb.tr.y + (1 - _miny + sy)) / _sliceWidth + 1)) + 1;
if (smin > smax)
return false;
bool collides = false;
bool nooverlap = true;
for (int i = smin; i <= smax; ++i)
{
float here = _edges[i] * rtl;
if (here > (float)9e37)
continue;
if (!_hit || x > here - _mingap - currSpace)
{
float y = (float)(_miny - 1 + (i + .5f) * _sliceWidth); // vertical center of slice
// 2 * currSpace to account for the space that is already separating them and the space we want to add
float m = get_edge(seg, slot, currShift, y, _sliceWidth, 0., rtl > 0) * rtl + 2 * currSpace;
if (m < (float)-8e37) // only true if the glyph has a gap in it
continue;
nooverlap = false;
float t = here - m;
// _mingap is positive to shrink
if (t < _mingap || (!_hit && !collides))
{
_mingap = t;
collides = true;
}
#if !defined GRAPHITE2_NTRACING
// Debugging - remember the closest neighboring edge for this slice.
if (m > rtl * _nearEdges[i])
{
_slotNear[i] = slot;
_nearEdges[i] = m * rtl;
}
#endif
}
else
nooverlap = false;
}
if (nooverlap)
_mingap = max(_mingap, _xbound - rtl * (currSpace + _margin + x));
if (collides && !nooverlap)
_hit = true;
return collides | nooverlap; // note that true is not a necessarily reliable value
} // end of KernCollider::mergeSlot
// Return the amount to kern by.
Position KernCollider::resolve(GR_MAYBE_UNUSED Segment *seg, GR_MAYBE_UNUSED Slot *slot,
int dir, GR_MAYBE_UNUSED json * const dbgout)
{
float resultNeeded = (1 - 2 * (dir & 1)) * _mingap;
// float resultNeeded = (1 - 2 * (dir & 1)) * (_mingap - margin);
float result = min(_limit.tr.x - _offsetPrev.x, max(resultNeeded, _limit.bl.x - _offsetPrev.x));
#if !defined GRAPHITE2_NTRACING
if (dbgout)
{
*dbgout << json::object // slot
<< "slot" << objectid(dslot(seg, _target))
<< "gid" << _target->gid()
<< "limit" << _limit
<< "miny" << _miny
<< "maxy" << _maxy
<< "slicewidth" << _sliceWidth
<< "target" << json::object
<< "origin" << _target->origin()
//<< "currShift" << _currShift
<< "offsetPrev" << _offsetPrev
<< "bbox" << seg->theGlyphBBoxTemporary(_target->gid())
<< "slantBox" << seg->getFace()->glyphs().slant(_target->gid())
<< "fix" << "kern"
<< json::close; // target object
*dbgout << "slices" << json::array;
for (int is = 0; is < (int)_edges.size(); is++)
{
*dbgout << json::flat << json::object
<< "i" << is
<< "targetEdge" << _edges[is]
<< "neighbor" << objectid(dslot(seg, _slotNear[is]))
<< "nearEdge" << _nearEdges[is]
<< json::close;
}
*dbgout << json::close; // slices array
*dbgout
<< "xbound" << _xbound
<< "minGap" << _mingap
<< "needed" << resultNeeded
<< "result" << result
<< "stillBad" << (result != resultNeeded)
<< json::close; // slot object
}
#endif
return Position(result, 0.);
} // end of KernCollider::resolve
void KernCollider::shift(const Position &mv, int dir)
{
for (Vector<float>::iterator e = _edges.begin(); e != _edges.end(); ++e)
*e += mv.x;
_xbound += (1 - 2 * (dir & 1)) * mv.x;
}
//// SLOT-COLLISION ////
// Initialize the collision attributes for the given slot.
SlotCollision::SlotCollision(Segment *seg, Slot *slot)
{
initFromSlot(seg, slot);
}
void SlotCollision::initFromSlot(Segment *seg, Slot *slot)
{
// Initialize slot attributes from glyph attributes.
// The order here must match the order in the grcompiler code,
// GrcSymbolTable::AssignInternalGlyphAttrIDs.
uint16 gid = slot->gid();
uint16 aCol = seg->silf()->aCollision(); // flags attr ID
const GlyphFace * glyphFace = seg->getFace()->glyphs().glyphSafe(gid);
if (!glyphFace)
return;
const sparse &p = glyphFace->attrs();
_flags = p[aCol];
_limit = Rect(Position(int16(p[aCol+1]), int16(p[aCol+2])),
Position(int16(p[aCol+3]), int16(p[aCol+4])));
_margin = p[aCol+5];
_marginWt = p[aCol+6];
_seqClass = p[aCol+7];
_seqProxClass = p[aCol+8];
_seqOrder = p[aCol+9];
_seqAboveXoff = p[aCol+10];
_seqAboveWt = p[aCol+11];
_seqBelowXlim = p[aCol+12];
_seqBelowWt = p[aCol+13];
_seqValignHt = p[aCol+14];
_seqValignWt = p[aCol+15];
// These attributes do not have corresponding glyph attribute:
_exclGlyph = 0;
_exclOffset = Position(0, 0);
}
float SlotCollision::getKern(int dir) const
{
if ((_flags & SlotCollision::COLL_KERN) != 0)
return float(_shift.x * ((dir & 1) ? -1 : 1));
else
return 0;
}
bool SlotCollision::ignore() const
{
return ((flags() & SlotCollision::COLL_IGNORE) || (flags() & SlotCollision::COLL_ISSPACE));
}