1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "Minikin" 18#include <cutils/log.h> 19 20#include <cmath> 21#include <unicode/uchar.h> 22 23#include <minikin/GraphemeBreak.h> 24#include <minikin/Measurement.h> 25 26namespace android { 27 28// These could be considered helper methods of layout, but need only be loosely coupled, so 29// are separate. 30 31static float getRunAdvance(const float* advances, const uint16_t* buf, size_t layoutStart, 32 size_t start, size_t count, size_t offset) { 33 float advance = 0.0f; 34 size_t lastCluster = start; 35 float clusterWidth = 0.0f; 36 for (size_t i = start; i < offset; i++) { 37 float charAdvance = advances[i - layoutStart]; 38 if (charAdvance != 0.0f) { 39 advance += charAdvance; 40 lastCluster = i; 41 clusterWidth = charAdvance; 42 } 43 } 44 if (offset < start + count && advances[offset - layoutStart] == 0.0f) { 45 // In the middle of a cluster, distribute width of cluster so that each grapheme cluster 46 // gets an equal share. 47 // TODO: get caret information out of font when that's available 48 size_t nextCluster; 49 for (nextCluster = offset + 1; nextCluster < start + count; nextCluster++) { 50 if (advances[nextCluster - layoutStart] != 0.0f) break; 51 } 52 int numGraphemeClusters = 0; 53 int numGraphemeClustersAfter = 0; 54 for (size_t i = lastCluster; i < nextCluster; i++) { 55 bool isAfter = i >= offset; 56 if (GraphemeBreak::isGraphemeBreak(buf, start, count, i)) { 57 numGraphemeClusters++; 58 if (isAfter) { 59 numGraphemeClustersAfter++; 60 } 61 } 62 } 63 if (numGraphemeClusters > 0) { 64 advance -= clusterWidth * numGraphemeClustersAfter / numGraphemeClusters; 65 } 66 } 67 return advance; 68} 69 70float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count, 71 size_t offset) { 72 return getRunAdvance(advances, buf, start, start, count, offset); 73} 74 75/** 76 * Essentially the inverse of getRunAdvance. Compute the value of offset for which the 77 * measured caret comes closest to the provided advance param, and which is on a grapheme 78 * cluster boundary. 79 * 80 * The actual implementation fast-forwards through clusters to get "close", then does a finer-grain 81 * search within the cluster and grapheme breaks. 82 */ 83size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count, 84 float advance) { 85 float x = 0.0f, xLastClusterStart = 0.0f, xSearchStart = 0.0f; 86 size_t lastClusterStart = start, searchStart = start; 87 for (size_t i = start; i < start + count; i++) { 88 if (GraphemeBreak::isGraphemeBreak(buf, start, count, i)) { 89 searchStart = lastClusterStart; 90 xSearchStart = xLastClusterStart; 91 } 92 float width = advances[i - start]; 93 if (width != 0.0f) { 94 lastClusterStart = i; 95 xLastClusterStart = x; 96 x += width; 97 if (x > advance) { 98 break; 99 } 100 } 101 } 102 size_t best = searchStart; 103 float bestDist = FLT_MAX; 104 for (size_t i = searchStart; i <= start + count; i++) { 105 if (GraphemeBreak::isGraphemeBreak(buf, start, count, i)) { 106 // "getRunAdvance(layout, buf, start, count, i) - advance" but more efficient 107 float delta = getRunAdvance(advances, buf, start, searchStart, count - searchStart, i) 108 109 + xSearchStart - advance; 110 if (std::abs(delta) < bestDist) { 111 bestDist = std::abs(delta); 112 best = i; 113 } 114 if (delta >= 0.0f) { 115 break; 116 } 117 } 118 } 119 return best; 120} 121 122} 123