Path: blob/master/thirdparty/graphite/src/Collider.cpp
9902 views
// SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later1// Copyright 2010, SIL International, All rights reserved.23#include <algorithm>4#include <limits>5#include <cmath>6#include <string>7#include <functional>8#include "inc/Collider.h"9#include "inc/Segment.h"10#include "inc/Slot.h"11#include "inc/GlyphCache.h"12#include "inc/Sparse.h"1314#define ISQRT2 0.707106781f1516// Possible rounding error for subbox boundaries: 0.016 = 1/64 = 1/256 * 417// (values in font range from 0..256)18// #define SUBBOX_RND_ERR 0.0161920using namespace graphite2;2122//// SHIFT-COLLIDER ////2324// Initialize the Collider to hold the basic movement limits for the25// target slot, the one we are focusing on fixing.26bool ShiftCollider::initSlot(Segment *seg, Slot *aSlot, const Rect &limit, float margin, float marginWeight,27const Position &currShift, const Position &currOffset, int dir, GR_MAYBE_UNUSED json * const dbgout)28{29int i;30float mx, mn;31float a, shift;32const GlyphCache &gc = seg->getFace()->glyphs();33unsigned short gid = aSlot->gid();34if (!gc.check(gid))35return false;36const BBox &bb = gc.getBoundingBBox(gid);37const SlantBox &sb = gc.getBoundingSlantBox(gid);38//float sx = aSlot->origin().x + currShift.x;39//float sy = aSlot->origin().y + currShift.y;40if (currOffset.x != 0.f || currOffset.y != 0.f)41_limit = Rect(limit.bl - currOffset, limit.tr - currOffset);42else43_limit = limit;44// For a ShiftCollider, these indices indicate which vector we are moving by:45// 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 slot46for (i = 0; i < 4; ++i)47{48switch (i) {49case 0 : // x direction50mn = _limit.bl.x + currOffset.x;51mx = _limit.tr.x + currOffset.x;52_len[i] = bb.xa - bb.xi;53a = currOffset.y + currShift.y;54_ranges[i].initialise<XY>(mn, mx, margin, marginWeight, a);55break;56case 1 : // y direction57mn = _limit.bl.y + currOffset.y;58mx = _limit.tr.y + currOffset.y;59_len[i] = bb.ya - bb.yi;60a = currOffset.x + currShift.x;61_ranges[i].initialise<XY>(mn, mx, margin, marginWeight, a);62break;63case 2 : // sum (negatively sloped diagonal boundaries)64// pick closest x,y limit boundaries in s direction65shift = currOffset.x + currOffset.y + currShift.x + currShift.y;66mn = -2 * min(currShift.x - _limit.bl.x, currShift.y - _limit.bl.y) + shift;67mx = 2 * min(_limit.tr.x - currShift.x, _limit.tr.y - currShift.y) + shift;68_len[i] = sb.sa - sb.si;69a = currOffset.x - currOffset.y + currShift.x - currShift.y;70_ranges[i].initialise<SD>(mn, mx, margin / ISQRT2, marginWeight, a);71break;72case 3 : // diff (positively sloped diagonal boundaries)73// pick closest x,y limit boundaries in d direction74shift = currOffset.x - currOffset.y + currShift.x - currShift.y;75mn = -2 * min(currShift.x - _limit.bl.x, _limit.tr.y - currShift.y) + shift;76mx = 2 * min(_limit.tr.x - currShift.x, currShift.y - _limit.bl.y) + shift;77_len[i] = sb.da - sb.di;78a = currOffset.x + currOffset.y + currShift.x + currShift.y;79_ranges[i].initialise<SD>(mn, mx, margin / ISQRT2, marginWeight, a);80break;81}82}8384_target = aSlot;85if ((dir & 1) == 0)86{87// For LTR, switch and negate x limits.88_limit.bl.x = -1 * limit.tr.x;89//_limit.tr.x = -1 * limit.bl.x;90}91_currOffset = currOffset;92_currShift = currShift;93_origin = aSlot->origin() - currOffset; // the original anchor position of the glyph9495_margin = margin;96_marginWt = marginWeight;9798SlotCollision *c = seg->collisionInfo(aSlot);99_seqClass = c->seqClass();100_seqProxClass = c->seqProxClass();101_seqOrder = c->seqOrder();102return true;103}104105template <class O>106float sdm(float vi, float va, float mx, float my, O op)107{108float res = 2 * mx - vi;109if (op(res, vi + 2 * my))110{111res = va + 2 * my;112if (op(res, 2 * mx - va))113res = mx + my;114}115return res;116}117118// 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 axis119void 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)120{121float a, c;122switch (axis) {123case 0 :124if (box.bl.y < org.y + bb.ya && box.tr.y > org.y + bb.yi && box.width() > 0)125{126a = org.y + 0.5f * (bb.yi + bb.ya);127c = 0.5f * (bb.xi + bb.xa);128if (isx)129_ranges[axis].weighted<XY>(box.bl.x - c, box.tr.x - c, weight, a, m,130(minright ? box.tr.x : box.bl.x) - c, a, 0, false);131else132_ranges[axis].weighted<XY>(box.bl.x - c, box.tr.x - c, weight, a, 0, 0, org.y,133m * (a * a + sqr((minright ? box.tr.y : box.bl.y) - 0.5f * (bb.yi + bb.ya))), false);134}135break;136case 1 :137if (box.bl.x < org.x + bb.xa && box.tr.x > org.x + bb.xi && box.height() > 0)138{139a = org.x + 0.5f * (bb.xi + bb.xa);140c = 0.5f * (bb.yi + bb.ya);141if (isx)142_ranges[axis].weighted<XY>(box.bl.y - c, box.tr.y - c, weight, a, 0, 0, org.x,143m * (a * a + sqr((minright ? box.tr.x : box.bl.x) - 0.5f * (bb.xi + bb.xa))), false);144else145_ranges[axis].weighted<XY>(box.bl.y - c, box.tr.y - c, weight, a, m,146(minright ? box.tr.y : box.bl.y) - c, a, 0, false);147}148break;149case 2 :150if (box.bl.x - box.tr.y < org.x - org.y + sb.da && box.tr.x - box.bl.y > org.x - org.y + sb.di)151{152float d = org.x - org.y + 0.5f * (sb.di + sb.da);153c = 0.5f * (sb.si + sb.sa);154float smax = min(2 * box.tr.x - d, 2 * box.tr.y + d);155float smin = max(2 * box.bl.x - d, 2 * box.bl.y + d);156if (smin > smax) return;157float si;158a = d;159if (isx)160si = 2 * (minright ? box.tr.x : box.bl.x) - a;161else162si = 2 * (minright ? box.tr.y : box.bl.y) + a;163_ranges[axis].weighted<SD>(smin - c, smax - c, weight / 2, a, m / 2, si, 0, 0, isx);164}165break;166case 3 :167if (box.bl.x + box.bl.y < org.x + org.y + sb.sa && box.tr.x + box.tr.y > org.x + org.y + sb.si)168{169float s = org.x + org.y + 0.5f * (sb.si + sb.sa);170c = 0.5f * (sb.di + sb.da);171float dmax = min(2 * box.tr.x - s, s - 2 * box.bl.y);172float dmin = max(2 * box.bl.x - s, s - 2 * box.tr.y);173if (dmin > dmax) return;174float di;175a = s;176if (isx)177di = 2 * (minright ? box.tr.x : box.bl.x) - a;178else179di = 2 * (minright ? box.tr.y : box.bl.y) + a;180_ranges[axis].weighted<SD>(dmin - c, dmax - c, weight / 2, a, m / 2, di, 0, 0, !isx);181}182break;183default :184break;185}186return;187}188189// Mark an area with an absolute cost, making it completely inaccessible.190inline void ShiftCollider::removeBox(const Rect &box, const BBox &bb, const SlantBox &sb, const Position &org, int axis)191{192float c;193switch (axis) {194case 0 :195if (box.bl.y < org.y + bb.ya && box.tr.y > org.y + bb.yi && box.width() > 0)196{197c = 0.5f * (bb.xi + bb.xa);198_ranges[axis].exclude(box.bl.x - c, box.tr.x - c);199}200break;201case 1 :202if (box.bl.x < org.x + bb.xa && box.tr.x > org.x + bb.xi && box.height() > 0)203{204c = 0.5f * (bb.yi + bb.ya);205_ranges[axis].exclude(box.bl.y - c, box.tr.y - c);206}207break;208case 2 :209if (box.bl.x - box.tr.y < org.x - org.y + sb.da && box.tr.x - box.bl.y > org.x - org.y + sb.di210&& box.width() > 0 && box.height() > 0)211{212float di = org.x - org.y + sb.di;213float da = org.x - org.y + sb.da;214float smax = sdm(di, da, box.tr.x, box.tr.y, std::greater<float>());215float smin = sdm(da, di, box.bl.x, box.bl.y, std::less<float>());216c = 0.5f * (sb.si + sb.sa);217_ranges[axis].exclude(smin - c, smax - c);218}219break;220case 3 :221if (box.bl.x + box.bl.y < org.x + org.y + sb.sa && box.tr.x + box.tr.y > org.x + org.y + sb.si222&& box.width() > 0 && box.height() > 0)223{224float si = org.x + org.y + sb.si;225float sa = org.x + org.y + sb.sa;226float dmax = sdm(si, sa, box.tr.x, -box.bl.y, std::greater<float>());227float dmin = sdm(sa, si, box.bl.x, -box.tr.y, std::less<float>());228c = 0.5f * (sb.di + sb.da);229_ranges[axis].exclude(dmin - c, dmax - c);230}231break;232default :233break;234}235return;236}237238// Adjust the movement limits for the target to avoid having it collide239// with the given neighbor slot. Also determine if there is in fact a collision240// between the target and the given slot.241bool ShiftCollider::mergeSlot(Segment *seg, Slot *slot, const SlotCollision *cslot, const Position &currShift,242bool isAfter, // slot is logically after _target243bool sameCluster, bool &hasCol, bool isExclusion,244GR_MAYBE_UNUSED json * const dbgout )245{246bool isCol = false;247const float sx = slot->origin().x - _origin.x + currShift.x;248const float sy = slot->origin().y - _origin.y + currShift.y;249const float sd = sx - sy;250const float ss = sx + sy;251float vmin, vmax;252float omin, omax, otmin, otmax;253float cmin, cmax; // target limits254float torg;255const GlyphCache &gc = seg->getFace()->glyphs();256const unsigned short gid = slot->gid();257if (!gc.check(gid))258return false;259const BBox &bb = gc.getBoundingBBox(gid);260261// SlotCollision * cslot = seg->collisionInfo(slot);262int orderFlags = 0;263bool sameClass = _seqProxClass == 0 && cslot->seqClass() == _seqClass;264if (sameCluster && _seqClass265&& (sameClass || (_seqProxClass != 0 && cslot->seqClass() == _seqProxClass)))266// Force the target glyph to be in the specified direction from the slot we're testing.267orderFlags = _seqOrder;268269// short circuit if only interested in direct collision and we are out of range270if (orderFlags || (sx + bb.xa + _margin >= _limit.bl.x && sx + bb.xi - _margin <= _limit.tr.x)271|| (sy + bb.ya + _margin >= _limit.bl.y && sy + bb.yi - _margin <= _limit.tr.y))272273{274const float tx = _currOffset.x + _currShift.x;275const float ty = _currOffset.y + _currShift.y;276const float td = tx - ty;277const float ts = tx + ty;278const SlantBox &sb = gc.getBoundingSlantBox(gid);279const unsigned short tgid = _target->gid();280const BBox &tbb = gc.getBoundingBBox(tgid);281const SlantBox &tsb = gc.getBoundingSlantBox(tgid);282float seq_above_wt = cslot->seqAboveWt();283float seq_below_wt = cslot->seqBelowWt();284float seq_valign_wt = cslot->seqValignWt();285float lmargin;286// if isAfter, invert orderFlags for diagonal orders.287if (isAfter)288{289// invert appropriate bits290orderFlags ^= (sameClass ? 0x3F : 0x3);291// consider 2 bits at a time, non overlapping. If both bits set, clear them292orderFlags = orderFlags ^ ((((orderFlags >> 1) & orderFlags) & 0x15) * 3);293}294295#if !defined GRAPHITE2_NTRACING296if (dbgout)297dbgout->setenv(0, slot);298#endif299300// Process main bounding octabox.301for (int i = 0; i < 4; ++i)302{303switch (i) {304case 0 : // x direction305vmin = max(max(bb.xi - tbb.xa + sx, sb.di - tsb.da + ty + sd), sb.si - tsb.sa - ty + ss);306vmax = min(min(bb.xa - tbb.xi + sx, sb.da - tsb.di + ty + sd), sb.sa - tsb.si - ty + ss);307otmin = tbb.yi + ty;308otmax = tbb.ya + ty;309omin = bb.yi + sy;310omax = bb.ya + sy;311torg = _currOffset.x;312cmin = _limit.bl.x + torg;313cmax = _limit.tr.x - tbb.xi + tbb.xa + torg;314lmargin = _margin;315break;316case 1 : // y direction317vmin = max(max(bb.yi - tbb.ya + sy, tsb.di - sb.da + tx - sd), sb.si - tsb.sa - tx + ss);318vmax = min(min(bb.ya - tbb.yi + sy, tsb.da - sb.di + tx - sd), sb.sa - tsb.si - tx + ss);319otmin = tbb.xi + tx;320otmax = tbb.xa + tx;321omin = bb.xi + sx;322omax = bb.xa + sx;323torg = _currOffset.y;324cmin = _limit.bl.y + torg;325cmax = _limit.tr.y - tbb.yi + tbb.ya + torg;326lmargin = _margin;327break;328case 2 : // sum - moving along the positively-sloped vector, so the boundaries are the329// negatively-sloped boundaries.330vmin = max(max(sb.si - tsb.sa + ss, 2 * (bb.yi - tbb.ya + sy) + td), 2 * (bb.xi - tbb.xa + sx) - td);331vmax = min(min(sb.sa - tsb.si + ss, 2 * (bb.ya - tbb.yi + sy) + td), 2 * (bb.xa - tbb.xi + sx) - td);332otmin = tsb.di + td;333otmax = tsb.da + td;334omin = sb.di + sd;335omax = sb.da + sd;336torg = _currOffset.x + _currOffset.y;337cmin = _limit.bl.x + _limit.bl.y + torg;338cmax = _limit.tr.x + _limit.tr.y - tsb.si + tsb.sa + torg;339lmargin = _margin / ISQRT2;340break;341case 3 : // diff - moving along the negatively-sloped vector, so the boundaries are the342// positively-sloped boundaries.343vmin = max(max(sb.di - tsb.da + sd, 2 * (bb.xi - tbb.xa + sx) - ts), -2 * (bb.ya - tbb.yi + sy) + ts);344vmax = min(min(sb.da - tsb.di + sd, 2 * (bb.xa - tbb.xi + sx) - ts), -2 * (bb.yi - tbb.ya + sy) + ts);345otmin = tsb.si + ts;346otmax = tsb.sa + ts;347omin = sb.si + ss;348omax = sb.sa + ss;349torg = _currOffset.x - _currOffset.y;350cmin = _limit.bl.x - _limit.tr.y + torg;351cmax = _limit.tr.x - _limit.bl.y - tsb.di + tsb.da + torg;352lmargin = _margin / ISQRT2;353break;354default :355continue;356}357358#if !defined GRAPHITE2_NTRACING359if (dbgout)360dbgout->setenv(1, reinterpret_cast<void *>(-1));361#define DBGTAG(x) if (dbgout) dbgout->setenv(1, reinterpret_cast<void *>(-x));362#else363#define DBGTAG(x)364#endif365366if (orderFlags)367{368Position org(tx, ty);369float xminf = _limit.bl.x + _currOffset.x + tbb.xi;370float xpinf = _limit.tr.x + _currOffset.x + tbb.xa;371float ypinf = _limit.tr.y + _currOffset.y + tbb.ya;372float yminf = _limit.bl.y + _currOffset.y + tbb.yi;373switch (orderFlags) {374case SlotCollision::SEQ_ORDER_RIGHTUP :375{376float r1Xedge = cslot->seqAboveXoff() + 0.5f * (bb.xi + bb.xa) + sx;377float r3Xedge = cslot->seqBelowXlim() + bb.xa + sx + 0.5f * (tbb.xa - tbb.xi);378float r2Yedge = 0.5f * (bb.yi + bb.ya) + sy;379380// DBGTAG(1x) means the regions are up and right381// region 1382DBGTAG(11)383addBox_slope(true, Rect(Position(xminf, r2Yedge), Position(r1Xedge, ypinf)),384tbb, tsb, org, 0, seq_above_wt, true, i);385// region 2386DBGTAG(12)387removeBox(Rect(Position(xminf, yminf), Position(r3Xedge, r2Yedge)), tbb, tsb, org, i);388// region 3, which end is zero is irrelevant since m weight is 0389DBGTAG(13)390addBox_slope(true, Rect(Position(r3Xedge, yminf), Position(xpinf, r2Yedge - cslot->seqValignHt())),391tbb, tsb, org, seq_below_wt, 0, true, i);392// region 4393DBGTAG(14)394addBox_slope(false, Rect(Position(sx + bb.xi, r2Yedge), Position(xpinf, r2Yedge + cslot->seqValignHt())),395tbb, tsb, org, 0, seq_valign_wt, true, i);396// region 5397DBGTAG(15)398addBox_slope(false, Rect(Position(sx + bb.xi, r2Yedge - cslot->seqValignHt()), Position(xpinf, r2Yedge)),399tbb, tsb, org, seq_below_wt, seq_valign_wt, false, i);400break;401}402case SlotCollision::SEQ_ORDER_LEFTDOWN :403{404float r1Xedge = 0.5f * (bb.xi + bb.xa) + cslot->seqAboveXoff() + sx;405float r3Xedge = bb.xi - cslot->seqBelowXlim() + sx - 0.5f * (tbb.xa - tbb.xi);406float r2Yedge = 0.5f * (bb.yi + bb.ya) + sy;407// DBGTAG(2x) means the regions are up and right408// region 1409DBGTAG(21)410addBox_slope(true, Rect(Position(r1Xedge, yminf), Position(xpinf, r2Yedge)),411tbb, tsb, org, 0, seq_above_wt, false, i);412// region 2413DBGTAG(22)414removeBox(Rect(Position(r3Xedge, r2Yedge), Position(xpinf, ypinf)), tbb, tsb, org, i);415// region 3416DBGTAG(23)417addBox_slope(true, Rect(Position(xminf, r2Yedge - cslot->seqValignHt()), Position(r3Xedge, ypinf)),418tbb, tsb, org, seq_below_wt, 0, false, i);419// region 4420DBGTAG(24)421addBox_slope(false, Rect(Position(xminf, r2Yedge), Position(sx + bb.xa, r2Yedge + cslot->seqValignHt())),422tbb, tsb, org, 0, seq_valign_wt, true, i);423// region 5424DBGTAG(25)425addBox_slope(false, Rect(Position(xminf, r2Yedge - cslot->seqValignHt()),426Position(sx + bb.xa, r2Yedge)), tbb, tsb, org, seq_below_wt, seq_valign_wt, false, i);427break;428}429case SlotCollision::SEQ_ORDER_NOABOVE : // enforce neighboring glyph being above430DBGTAG(31);431removeBox(Rect(Position(bb.xi - tbb.xa + sx, sy + bb.ya),432Position(bb.xa - tbb.xi + sx, ypinf)), tbb, tsb, org, i);433break;434case SlotCollision::SEQ_ORDER_NOBELOW : // enforce neighboring glyph being below435DBGTAG(32);436removeBox(Rect(Position(bb.xi - tbb.xa + sx, yminf),437Position(bb.xa - tbb.xi + sx, sy + bb.yi)), tbb, tsb, org, i);438break;439case SlotCollision::SEQ_ORDER_NOLEFT : // enforce neighboring glyph being to the left440DBGTAG(33)441removeBox(Rect(Position(xminf, bb.yi - tbb.ya + sy),442Position(bb.xi - tbb.xa + sx, bb.ya - tbb.yi + sy)), tbb, tsb, org, i);443break;444case SlotCollision::SEQ_ORDER_NORIGHT : // enforce neighboring glyph being to the right445DBGTAG(34)446removeBox(Rect(Position(bb.xa - tbb.xi + sx, bb.yi - tbb.ya + sy),447Position(xpinf, bb.ya - tbb.yi + sy)), tbb, tsb, org, i);448break;449default :450break;451}452}453454if (vmax < cmin - lmargin || vmin > cmax + lmargin || omax < otmin - lmargin || omin > otmax + lmargin)455continue;456457// Process sub-boxes that are defined for this glyph.458// We only need to do this if there was in fact a collision with the main octabox.459uint8 numsub = gc.numSubBounds(gid);460if (numsub > 0)461{462bool anyhits = false;463for (int j = 0; j < numsub; ++j)464{465const BBox &sbb = gc.getSubBoundingBBox(gid, j);466const SlantBox &ssb = gc.getSubBoundingSlantBox(gid, j);467switch (i) {468case 0 : // x469vmin = max(max(sbb.xi-tbb.xa+sx, ssb.di-tsb.da+sd+ty), ssb.si-tsb.sa+ss-ty);470vmax = min(min(sbb.xa-tbb.xi+sx, ssb.da-tsb.di+sd+ty), ssb.sa-tsb.si+ss-ty);471omin = sbb.yi + sy;472omax = sbb.ya + sy;473break;474case 1 : // y475vmin = max(max(sbb.yi-tbb.ya+sy, tsb.di-ssb.da-sd+tx), ssb.si-tsb.sa+ss-tx);476vmax = min(min(sbb.ya-tbb.yi+sy, tsb.da-ssb.di-sd+tx), ssb.sa-tsb.si+ss-tx);477omin = sbb.xi + sx;478omax = sbb.xa + sx;479break;480case 2 : // sum481vmin = max(max(ssb.si-tsb.sa+ss, 2*(sbb.yi-tbb.ya+sy)+td), 2*(sbb.xi-tbb.xa+sx)-td);482vmax = min(min(ssb.sa-tsb.si+ss, 2*(sbb.ya-tbb.yi+sy)+td), 2*(sbb.xa-tbb.xi+sx)-td);483omin = ssb.di + sd;484omax = ssb.da + sd;485break;486case 3 : // diff487vmin = max(max(ssb.di-tsb.da+sd, 2*(sbb.xi-tbb.xa+sx)-ts), -2*(sbb.ya-tbb.yi+sy)+ts);488vmax = min(min(ssb.da-tsb.di+sd, 2*(sbb.xa-tbb.xi+sx)-ts), -2*(sbb.yi-tbb.ya+sy)+ts);489omin = ssb.si + ss;490omax = ssb.sa + ss;491break;492}493if (vmax < cmin - lmargin || vmin > cmax + lmargin || omax < otmin - lmargin || omin > otmax + lmargin)494continue;495496#if !defined GRAPHITE2_NTRACING497if (dbgout)498dbgout->setenv(1, reinterpret_cast<void *>(j));499#endif500if (omin > otmax)501_ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0,502sqr(lmargin - omin + otmax) * _marginWt, false);503else if (omax < otmin)504_ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0,505sqr(lmargin - otmin + omax) * _marginWt, false);506else507_ranges[i].exclude_with_margins(vmin, vmax, i);508anyhits = true;509}510if (anyhits)511isCol = true;512}513else // no sub-boxes514{515#if !defined GRAPHITE2_NTRACING516if (dbgout)517dbgout->setenv(1, reinterpret_cast<void *>(-1));518#endif519isCol = true;520if (omin > otmax)521_ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0,522sqr(lmargin - omin + otmax) * _marginWt, false);523else if (omax < otmin)524_ranges[i].weightedAxis(i, vmin - lmargin, vmax + lmargin, 0, 0, 0, 0, 0,525sqr(lmargin - otmin + omax) * _marginWt, false);526else527_ranges[i].exclude_with_margins(vmin, vmax, i);528529}530}531}532bool res = true;533if (cslot->exclGlyph() > 0 && gc.check(cslot->exclGlyph()) && !isExclusion)534{535// Set up the bogus slot representing the exclusion glyph.536Slot *exclSlot = seg->newSlot();537if (!exclSlot)538return res;539exclSlot->setGlyph(seg, cslot->exclGlyph());540Position exclOrigin(slot->origin() + cslot->exclOffset());541exclSlot->origin(exclOrigin);542SlotCollision exclInfo(seg, exclSlot);543res &= mergeSlot(seg, exclSlot, &exclInfo, currShift, isAfter, sameCluster, isCol, true, dbgout );544seg->freeSlot(exclSlot);545}546hasCol |= isCol;547return res;548549} // end of ShiftCollider::mergeSlot550551552// Figure out where to move the target glyph to, and return the amount to shift by.553Position ShiftCollider::resolve(GR_MAYBE_UNUSED Segment *seg, bool &isCol, GR_MAYBE_UNUSED json * const dbgout)554{555float tbase;556float totalCost = (float)(std::numeric_limits<float>::max() / 2);557Position resultPos = Position(0, 0);558#if !defined GRAPHITE2_NTRACING559int bestAxis = -1;560if (dbgout)561{562outputJsonDbgStartSlot(dbgout, seg);563*dbgout << "vectors" << json::array;564}565#endif566isCol = true;567for (int i = 0; i < 4; ++i)568{569float bestCost = -1;570float bestPos;571// Calculate the margin depending on whether we are moving diagonally or not:572switch (i) {573case 0 : // x direction574tbase = _currOffset.x;575break;576case 1 : // y direction577tbase = _currOffset.y;578break;579case 2 : // sum (negatively-sloped diagonals)580tbase = _currOffset.x + _currOffset.y;581break;582case 3 : // diff (positively-sloped diagonals)583tbase = _currOffset.x - _currOffset.y;584break;585}586Position testp;587bestPos = _ranges[i].closest(0, bestCost) - tbase; // Get the best relative position588#if !defined GRAPHITE2_NTRACING589if (dbgout)590outputJsonDbgOneVector(dbgout, seg, i, tbase, bestCost, bestPos) ;591#endif592if (bestCost >= 0.0f)593{594isCol = false;595switch (i) {596case 0 : testp = Position(bestPos, _currShift.y); break;597case 1 : testp = Position(_currShift.x, bestPos); break;598case 2 : testp = Position(0.5f * (_currShift.x - _currShift.y + bestPos), 0.5f * (_currShift.y - _currShift.x + bestPos)); break;599case 3 : testp = Position(0.5f * (_currShift.x + _currShift.y + bestPos), 0.5f * (_currShift.x + _currShift.y - bestPos)); break;600}601if (bestCost < totalCost - 0.01f)602{603totalCost = bestCost;604resultPos = testp;605#if !defined GRAPHITE2_NTRACING606bestAxis = i;607#endif608}609}610} // end of loop over 4 directions611612#if !defined GRAPHITE2_NTRACING613if (dbgout)614outputJsonDbgEndSlot(dbgout, resultPos, bestAxis, isCol);615#endif616617return resultPos;618619} // end of ShiftCollider::resolve620621622#if !defined GRAPHITE2_NTRACING623624void ShiftCollider::outputJsonDbg(json * const dbgout, Segment *seg, int axis)625{626int axisMax = axis;627if (axis < 0) // output all axes628{629*dbgout << "gid" << _target->gid()630<< "limit" << _limit631<< "target" << json::object632<< "origin" << _target->origin()633<< "margin" << _margin634<< "bbox" << seg->theGlyphBBoxTemporary(_target->gid())635<< "slantbox" << seg->getFace()->glyphs().slant(_target->gid())636<< json::close; // target object637*dbgout << "ranges" << json::array;638axis = 0;639axisMax = 3;640}641for (int iAxis = axis; iAxis <= axisMax; ++iAxis)642{643*dbgout << json::flat << json::array << _ranges[iAxis].position();644for (Zones::const_iterator s = _ranges[iAxis].begin(), e = _ranges[iAxis].end(); s != e; ++s)645*dbgout << json::flat << json::array646<< Position(s->x, s->xm) << s->sm << s->smx << s->c647<< json::close;648*dbgout << json::close;649}650if (axis < axisMax) // looped through the _ranges array for all axes651*dbgout << json::close; // ranges array652}653654void ShiftCollider::outputJsonDbgStartSlot(json * const dbgout, Segment *seg)655{656*dbgout << json::object // slot - not closed till the end of the caller method657<< "slot" << objectid(dslot(seg, _target))658<< "gid" << _target->gid()659<< "limit" << _limit660<< "target" << json::object661<< "origin" << _origin662<< "currShift" << _currShift663<< "currOffset" << seg->collisionInfo(_target)->offset()664<< "bbox" << seg->theGlyphBBoxTemporary(_target->gid())665<< "slantBox" << seg->getFace()->glyphs().slant(_target->gid())666<< "fix" << "shift";667*dbgout << json::close; // target object668}669670void ShiftCollider::outputJsonDbgEndSlot(GR_MAYBE_UNUSED json * const dbgout,671Position resultPos, int bestAxis, bool isCol)672{673*dbgout << json::close // vectors array674<< "result" << resultPos675//<< "scraping" << _scraping[bestAxis]676<< "bestAxis" << bestAxis677<< "stillBad" << isCol678<< json::close; // slot object679}680681void ShiftCollider::outputJsonDbgOneVector(json * const dbgout, Segment *seg, int axis,682float tleft, float bestCost, float bestVal)683{684const char * label;685switch (axis)686{687case 0: label = "x"; break;688case 1: label = "y"; break;689case 2: label = "sum (NE-SW)"; break;690case 3: label = "diff (NW-SE)"; break;691default: label = "???"; break;692}693694*dbgout << json::object // vector695<< "direction" << label696<< "targetMin" << tleft;697698outputJsonDbgRemovals(dbgout, axis, seg);699700*dbgout << "ranges";701outputJsonDbg(dbgout, seg, axis);702703*dbgout << "bestCost" << bestCost704<< "bestVal" << bestVal + tleft705<< json::close; // vectors object706}707708void ShiftCollider::outputJsonDbgRemovals(json * const dbgout, int axis, Segment *seg)709{710*dbgout << "removals" << json::array;711_ranges[axis].jsonDbgOut(seg);712*dbgout << json::close; // removals array713}714715#endif // !defined GRAPHITE2_NTRACING716717718//// KERN-COLLIDER ////719720inline721static float localmax (float al, float au, float bl, float bu, float x)722{723if (al < bl)724{ if (au < bu) return au < x ? au : x; }725else if (au > bu) return bl < x ? bl : x;726return x;727}728729inline730static float localmin(float al, float au, float bl, float bu, float x)731{732if (bl > al)733{ if (bu > au) return bl > x ? bl : x; }734else if (au > bu) return al > x ? al : x;735return x;736}737738// Return the given edge of the glyph at height y, taking any slant box into account.739static float get_edge(Segment *seg, const Slot *s, const Position &shift, float y, float width, float margin, bool isRight)740{741const GlyphCache &gc = seg->getFace()->glyphs();742unsigned short gid = s->gid();743float sx = s->origin().x + shift.x;744float sy = s->origin().y + shift.y;745uint8 numsub = gc.numSubBounds(gid);746float res = isRight ? (float)-1e38 : (float)1e38;747748if (numsub > 0)749{750for (int i = 0; i < numsub; ++i)751{752const BBox &sbb = gc.getSubBoundingBBox(gid, i);753const SlantBox &ssb = gc.getSubBoundingSlantBox(gid, i);754if (sy + sbb.yi - margin > y + width / 2 || sy + sbb.ya + margin < y - width / 2)755continue;756if (isRight)757{758float x = sx + sbb.xa + margin;759if (x > res)760{761float td = sx - sy + ssb.da + margin + y;762float ts = sx + sy + ssb.sa + margin - y;763x = localmax(td - width / 2, td + width / 2, ts - width / 2, ts + width / 2, x);764if (x > res)765res = x;766}767}768else769{770float x = sx + sbb.xi - margin;771if (x < res)772{773float td = sx - sy + ssb.di - margin + y;774float ts = sx + sy + ssb.si - margin - y;775x = localmin(td - width / 2, td + width / 2, ts - width / 2, ts + width / 2, x);776if (x < res)777res = x;778}779}780}781}782else783{784const BBox &bb = gc.getBoundingBBox(gid);785const SlantBox &sb = gc.getBoundingSlantBox(gid);786if (sy + bb.yi - margin > y + width / 2 || sy + bb.ya + margin < y - width / 2)787return res;788float td = sx - sy + y;789float ts = sx + sy - y;790if (isRight)791res = 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;792else793res = 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;794}795return res;796}797798799bool KernCollider::initSlot(Segment *seg, Slot *aSlot, const Rect &limit, float margin,800const Position &currShift, const Position &offsetPrev, int dir,801float ymin, float ymax, GR_MAYBE_UNUSED json * const dbgout)802{803const GlyphCache &gc = seg->getFace()->glyphs();804const Slot *base = aSlot;805// const Slot *last = aSlot;806const Slot *s;807int numSlices;808while (base->attachedTo())809base = base->attachedTo();810if (margin < 10) margin = 10;811812_limit = limit;813_offsetPrev = offsetPrev; // kern from a previous pass814815// Calculate the height of the glyph and how many horizontal slices to use.816if (_maxy >= 1e37f)817{818_sliceWidth = margin / 1.5f;819_maxy = ymax + margin;820_miny = ymin - margin;821numSlices = int((_maxy - _miny + 2) / (_sliceWidth / 1.5f) + 1.f); // +2 helps with rounding errors822_edges.clear();823_edges.insert(_edges.begin(), numSlices, (dir & 1) ? 1e38f : -1e38f);824_xbound = (dir & 1) ? (float)1e38f : (float)-1e38f;825}826else if (_maxy != ymax || _miny != ymin)827{828if (_miny != ymin)829{830numSlices = int((ymin - margin - _miny) / _sliceWidth - 1);831_miny += numSlices * _sliceWidth;832if (numSlices < 0)833_edges.insert(_edges.begin(), -numSlices, (dir & 1) ? 1e38f : -1e38f);834else if ((unsigned)numSlices < _edges.size()) // this shouldn't fire since we always grow the range835{836Vector<float>::iterator e = _edges.begin();837while (numSlices--)838++e;839_edges.erase(_edges.begin(), e);840}841}842if (_maxy != ymax)843{844numSlices = int((ymax + margin - _miny) / _sliceWidth + 1);845_maxy = numSlices * _sliceWidth + _miny;846if (numSlices > (int)_edges.size())847_edges.insert(_edges.end(), numSlices - _edges.size(), (dir & 1) ? 1e38f : -1e38f);848else if (numSlices < (int)_edges.size()) // this shouldn't fire since we always grow the range849{850while ((int)_edges.size() > numSlices)851_edges.pop_back();852}853}854goto done;855}856numSlices = int(_edges.size());857858#if !defined GRAPHITE2_NTRACING859// Debugging860_seg = seg;861_slotNear.clear();862_slotNear.insert(_slotNear.begin(), numSlices, NULL);863_nearEdges.clear();864_nearEdges.insert(_nearEdges.begin(), numSlices, (dir & 1) ? -1e38f : +1e38f);865#endif866867// Determine the trailing edge of each slice (ie, left edge for a RTL glyph).868for (s = base; s; s = s->nextInCluster(s))869{870SlotCollision *c = seg->collisionInfo(s);871if (!gc.check(s->gid()))872return false;873const BBox &bs = gc.getBoundingBBox(s->gid());874float x = s->origin().x + c->shift().x + ((dir & 1) ? bs.xi : bs.xa);875// Loop over slices.876// Note smin might not be zero if glyph s is not at the bottom of the cluster; similarly for smax.877float toffset = c->shift().y - _miny + 1 + s->origin().y;878int smin = max(0, int((bs.yi + toffset) / _sliceWidth));879int smax = min(numSlices - 1, int((bs.ya + toffset) / _sliceWidth + 1));880for (int i = smin; i <= smax; ++i)881{882float t;883float y = _miny - 1 + (i + .5f) * _sliceWidth; // vertical center of slice884if ((dir & 1) && x < _edges[i])885{886t = get_edge(seg, s, c->shift(), y, _sliceWidth, margin, false);887if (t < _edges[i])888{889_edges[i] = t;890if (t < _xbound)891_xbound = t;892}893}894else if (!(dir & 1) && x > _edges[i])895{896t = get_edge(seg, s, c->shift(), y, _sliceWidth, margin, true);897if (t > _edges[i])898{899_edges[i] = t;900if (t > _xbound)901_xbound = t;902}903}904}905}906done:907_mingap = (float)1e37; // less than 1e38 s.t. 1e38-_mingap is really big908_target = aSlot;909_margin = margin;910_currShift = currShift;911return true;912} // end of KernCollider::initSlot913914915// Determine how much the target slot needs to kern away from the given slot.916// In other words, merge information from given slot's position with what the target slot knows917// about how it can kern.918// Return false if we know there is no collision, true if we think there might be one.919bool KernCollider::mergeSlot(Segment *seg, Slot *slot, const Position &currShift, float currSpace, int dir, GR_MAYBE_UNUSED json * const dbgout)920{921int rtl = (dir & 1) * 2 - 1;922if (!seg->getFace()->glyphs().check(slot->gid()))923return false;924const Rect &bb = seg->theGlyphBBoxTemporary(slot->gid());925const float sx = slot->origin().x + currShift.x;926float x = (sx + (rtl > 0 ? bb.tr.x : bb.bl.x)) * rtl;927// this isn't going to reduce _mingap so skip928if (_hit && x < rtl * (_xbound - _mingap - currSpace))929return false;930931const float sy = slot->origin().y + currShift.y;932int smin = max(1, int((bb.bl.y + (1 - _miny + sy)) / _sliceWidth + 1)) - 1;933int smax = min((int)_edges.size() - 2, int((bb.tr.y + (1 - _miny + sy)) / _sliceWidth + 1)) + 1;934if (smin > smax)935return false;936bool collides = false;937bool nooverlap = true;938939for (int i = smin; i <= smax; ++i)940{941float here = _edges[i] * rtl;942if (here > (float)9e37)943continue;944if (!_hit || x > here - _mingap - currSpace)945{946float y = (float)(_miny - 1 + (i + .5f) * _sliceWidth); // vertical center of slice947// 2 * currSpace to account for the space that is already separating them and the space we want to add948float m = get_edge(seg, slot, currShift, y, _sliceWidth, 0., rtl > 0) * rtl + 2 * currSpace;949if (m < (float)-8e37) // only true if the glyph has a gap in it950continue;951nooverlap = false;952float t = here - m;953// _mingap is positive to shrink954if (t < _mingap || (!_hit && !collides))955{956_mingap = t;957collides = true;958}959#if !defined GRAPHITE2_NTRACING960// Debugging - remember the closest neighboring edge for this slice.961if (m > rtl * _nearEdges[i])962{963_slotNear[i] = slot;964_nearEdges[i] = m * rtl;965}966#endif967}968else969nooverlap = false;970}971if (nooverlap)972_mingap = max(_mingap, _xbound - rtl * (currSpace + _margin + x));973if (collides && !nooverlap)974_hit = true;975return collides | nooverlap; // note that true is not a necessarily reliable value976977} // end of KernCollider::mergeSlot978979980// Return the amount to kern by.981Position KernCollider::resolve(GR_MAYBE_UNUSED Segment *seg, GR_MAYBE_UNUSED Slot *slot,982int dir, GR_MAYBE_UNUSED json * const dbgout)983{984float resultNeeded = (1 - 2 * (dir & 1)) * _mingap;985// float resultNeeded = (1 - 2 * (dir & 1)) * (_mingap - margin);986float result = min(_limit.tr.x - _offsetPrev.x, max(resultNeeded, _limit.bl.x - _offsetPrev.x));987988#if !defined GRAPHITE2_NTRACING989if (dbgout)990{991*dbgout << json::object // slot992<< "slot" << objectid(dslot(seg, _target))993<< "gid" << _target->gid()994<< "limit" << _limit995<< "miny" << _miny996<< "maxy" << _maxy997<< "slicewidth" << _sliceWidth998<< "target" << json::object999<< "origin" << _target->origin()1000//<< "currShift" << _currShift1001<< "offsetPrev" << _offsetPrev1002<< "bbox" << seg->theGlyphBBoxTemporary(_target->gid())1003<< "slantBox" << seg->getFace()->glyphs().slant(_target->gid())1004<< "fix" << "kern"1005<< json::close; // target object10061007*dbgout << "slices" << json::array;1008for (int is = 0; is < (int)_edges.size(); is++)1009{1010*dbgout << json::flat << json::object1011<< "i" << is1012<< "targetEdge" << _edges[is]1013<< "neighbor" << objectid(dslot(seg, _slotNear[is]))1014<< "nearEdge" << _nearEdges[is]1015<< json::close;1016}1017*dbgout << json::close; // slices array10181019*dbgout1020<< "xbound" << _xbound1021<< "minGap" << _mingap1022<< "needed" << resultNeeded1023<< "result" << result1024<< "stillBad" << (result != resultNeeded)1025<< json::close; // slot object1026}1027#endif10281029return Position(result, 0.);10301031} // end of KernCollider::resolve10321033void KernCollider::shift(const Position &mv, int dir)1034{1035for (Vector<float>::iterator e = _edges.begin(); e != _edges.end(); ++e)1036*e += mv.x;1037_xbound += (1 - 2 * (dir & 1)) * mv.x;1038}10391040//// SLOT-COLLISION ////10411042// Initialize the collision attributes for the given slot.1043SlotCollision::SlotCollision(Segment *seg, Slot *slot)1044{1045initFromSlot(seg, slot);1046}10471048void SlotCollision::initFromSlot(Segment *seg, Slot *slot)1049{1050// Initialize slot attributes from glyph attributes.1051// The order here must match the order in the grcompiler code,1052// GrcSymbolTable::AssignInternalGlyphAttrIDs.1053uint16 gid = slot->gid();1054uint16 aCol = seg->silf()->aCollision(); // flags attr ID1055const GlyphFace * glyphFace = seg->getFace()->glyphs().glyphSafe(gid);1056if (!glyphFace)1057return;1058const sparse &p = glyphFace->attrs();1059_flags = p[aCol];1060_limit = Rect(Position(int16(p[aCol+1]), int16(p[aCol+2])),1061Position(int16(p[aCol+3]), int16(p[aCol+4])));1062_margin = p[aCol+5];1063_marginWt = p[aCol+6];10641065_seqClass = p[aCol+7];1066_seqProxClass = p[aCol+8];1067_seqOrder = p[aCol+9];1068_seqAboveXoff = p[aCol+10];1069_seqAboveWt = p[aCol+11];1070_seqBelowXlim = p[aCol+12];1071_seqBelowWt = p[aCol+13];1072_seqValignHt = p[aCol+14];1073_seqValignWt = p[aCol+15];10741075// These attributes do not have corresponding glyph attribute:1076_exclGlyph = 0;1077_exclOffset = Position(0, 0);1078}10791080float SlotCollision::getKern(int dir) const1081{1082if ((_flags & SlotCollision::COLL_KERN) != 0)1083return float(_shift.x * ((dir & 1) ? -1 : 1));1084else1085return 0;1086}10871088bool SlotCollision::ignore() const1089{1090return ((flags() & SlotCollision::COLL_IGNORE) || (flags() & SlotCollision::COLL_ISSPACE));1091}109210931094