Measurement.cpp revision 40beb7744a61248de82a6077996c83c14e0122c2
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
31float getRunAdvance(Layout& layout, const uint16_t* buf, size_t start, size_t count,
32        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 = layout.getCharAdvance(i - start);
38        if (charAdvance != 0.0f) {
39            advance += charAdvance;
40            lastCluster = i;
41            clusterWidth = charAdvance;
42        }
43    }
44    if (offset < start + count && layout.getCharAdvance(offset) == 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 (layout.getCharAdvance(nextCluster - start) != 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
70/**
71 * Essentially the inverse of getRunAdvance. Compute the value of offset for which the
72 * measured caret comes closest to the provided advance param, and which is on a grapheme
73 * cluster boundary.
74 *
75 * The actual implementation fast-forwards through clusters to get "close", then does a finer-grain
76 * search within the cluster and grapheme breaks.
77 */
78size_t getOffsetForAdvance(Layout& layout, const uint16_t* buf, size_t start, size_t count,
79        float advance) {
80    float x = 0.0f, xLastClusterStart = 0.0f, xSearchStart = 0.0f;
81    size_t lastClusterStart = start, searchStart = start;
82    for (size_t i = start; i < start + count; i++) {
83        if (GraphemeBreak::isGraphemeBreak(buf, start, count, i)) {
84            searchStart = lastClusterStart;
85            xSearchStart = xLastClusterStart;
86        }
87        float width = layout.getCharAdvance(i - start);
88        if (width != 0.0f) {
89            lastClusterStart = i;
90            xLastClusterStart = x;
91            x += width;
92            if (x > advance) {
93                break;
94            }
95        }
96    }
97    size_t best = searchStart;
98    float bestDist = FLT_MAX;
99    for (size_t i = searchStart; i <= start + count; i++) {
100        if (GraphemeBreak::isGraphemeBreak(buf, start, count, i)) {
101            // "getRunAdvance(layout, buf, start, count, bufSize, i) - advance" but more efficient
102            float delta = getRunAdvance(layout, buf, searchStart, count, i) + xSearchStart
103                    - advance;
104            if (std::abs(delta) < bestDist) {
105                bestDist = std::abs(delta);
106                best = i;
107            }
108            if (delta >= 0.0f) {
109                break;
110            }
111        }
112    }
113    return best;
114}
115
116}
117