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 internet at http://www.fsf.org/licenses/lgpl.html.
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
usingnamespace 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(); unsignedshort gid = aSlot->gid(); if (!gc.check(gid)) returnfalse; 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
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. inlinevoid 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; constfloat sx = slot->origin().x - _origin.x + currShift.x; constfloat sy = slot->origin().y - _origin.y + currShift.y; constfloat sd = sx - sy; constfloat ss = sx + sy; float vmin, vmax; float omin, omax, otmin, otmax; float cmin, cmax; // target limits float torg; const GlyphCache &gc = seg->getFace()->glyphs(); constunsignedshort gid = slot->gid(); if (!gc.check(gid)) returnfalse; 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))
{ constfloat tx = _currOffset.x + _currShift.x; constfloat ty = _currOffset.y + _currShift.y; constfloat td = tx - ty; constfloat ts = tx + ty; const SlantBox &sb = gc.getBoundingSlantBox(gid); constunsignedshort 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
// 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())) returnfalse; 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;
}
} elseif (!(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; returntrue;
} // 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())) returnfalse; const Rect &bb = seg->theGlyphBBoxTemporary(slot->gid()); constfloat 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)) returnfalse;
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
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.