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 #include "minikin/Measurement.h"
18
19 #include <cfloat>
20 #include <cmath>
21
22 #include "BidiUtils.h"
23 #include "LayoutSplitter.h"
24 #include "minikin/BoundsCache.h"
25 #include "minikin/GraphemeBreak.h"
26
27 namespace minikin {
28
29 // These could be considered helper methods of layout, but need only be loosely coupled, so
30 // are separate.
31
getRunAdvance(const float * advances,const uint16_t * buf,size_t layoutStart,size_t start,size_t count,size_t offset)32 static float getRunAdvance(const float* advances, const uint16_t* buf, size_t layoutStart,
33 size_t start, size_t count, size_t offset) {
34 float advance = 0.0f;
35 size_t lastCluster = start;
36 float clusterWidth = 0.0f;
37 for (size_t i = start; i < offset; i++) {
38 float charAdvance = advances[i - layoutStart];
39 if (charAdvance != 0.0f) {
40 advance += charAdvance;
41 lastCluster = i;
42 clusterWidth = charAdvance;
43 }
44 }
45 if (offset < start + count && advances[offset - layoutStart] == 0.0f) {
46 // In the middle of a cluster, distribute width of cluster so that each grapheme cluster
47 // gets an equal share.
48 // TODO: get caret information out of font when that's available
49 size_t nextCluster;
50 for (nextCluster = offset + 1; nextCluster < start + count; nextCluster++) {
51 if (advances[nextCluster - layoutStart] != 0.0f) break;
52 }
53 int numGraphemeClusters = 0;
54 int numGraphemeClustersAfter = 0;
55 for (size_t i = lastCluster; i < nextCluster; i++) {
56 bool isAfter = i >= offset;
57 if (GraphemeBreak::isGraphemeBreak(advances + (start - layoutStart), buf, start, count,
58 i)) {
59 numGraphemeClusters++;
60 if (isAfter) {
61 numGraphemeClustersAfter++;
62 }
63 }
64 }
65 if (numGraphemeClusters > 0) {
66 advance -= clusterWidth * numGraphemeClustersAfter / numGraphemeClusters;
67 }
68 }
69 return advance;
70 }
71
getRunAdvance(const float * advances,const uint16_t * buf,size_t start,size_t count,size_t offset)72 float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
73 size_t offset) {
74 return getRunAdvance(advances, buf, start, start, count, offset);
75 }
76
77 /**
78 * Essentially the inverse of getRunAdvance. Compute the value of offset for which the
79 * measured caret comes closest to the provided advance param, and which is on a grapheme
80 * cluster boundary.
81 *
82 * The actual implementation fast-forwards through clusters to get "close", then does a finer-grain
83 * search within the cluster and grapheme breaks.
84 */
getOffsetForAdvance(const float * advances,const uint16_t * buf,size_t start,size_t count,float advance)85 size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
86 float advance) {
87 float x = 0.0f, xLastClusterStart = 0.0f, xSearchStart = 0.0f;
88 size_t lastClusterStart = start, searchStart = start;
89 for (size_t i = start; i < start + count; i++) {
90 if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
91 searchStart = lastClusterStart;
92 xSearchStart = xLastClusterStart;
93 }
94 float width = advances[i - start];
95 if (width != 0.0f) {
96 lastClusterStart = i;
97 xLastClusterStart = x;
98 x += width;
99 if (x > advance) {
100 break;
101 }
102 }
103 }
104 size_t best = searchStart;
105 float bestDist = FLT_MAX;
106 for (size_t i = searchStart; i <= start + count; i++) {
107 if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
108 // "getRunAdvance(layout, buf, start, count, i) - advance" but more efficient
109 float delta = getRunAdvance(advances, buf, start, searchStart, count - searchStart, i)
110
111 + xSearchStart - advance;
112 if (std::abs(delta) < bestDist) {
113 bestDist = std::abs(delta);
114 best = i;
115 }
116 if (delta >= 0.0f) {
117 break;
118 }
119 }
120 }
121 return best;
122 }
123
124 struct BoundsComposer {
BoundsComposerminikin::BoundsComposer125 BoundsComposer() : mAdvance(0) {}
126
operator ()minikin::BoundsComposer127 void operator()(const MinikinRect& rect, float advance) {
128 MinikinRect tmp = rect;
129 tmp.offset(mAdvance, 0);
130 mBounds.join(tmp);
131 mAdvance += advance;
132 }
133
134 float mAdvance;
135 MinikinRect mBounds;
136 };
137
getBounds(const U16StringPiece & str,const Range & range,Bidi bidiFlag,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,MinikinRect * out)138 void getBounds(const U16StringPiece& str, const Range& range, Bidi bidiFlag,
139 const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
140 MinikinRect* out) {
141 BoundsComposer bc;
142 for (const BidiText::RunInfo info : BidiText(str, range, bidiFlag)) {
143 for (const auto [context, piece] : LayoutSplitter(str, info.range, info.isRtl)) {
144 const StartHyphenEdit pieceStartHyphen =
145 (piece.getStart() == range.getStart()) ? startHyphen : StartHyphenEdit::NO_EDIT;
146 const EndHyphenEdit pieceEndHyphen =
147 (piece.getEnd() == range.getEnd()) ? endHyphen : EndHyphenEdit::NO_EDIT;
148 BoundsCache::getInstance().getOrCreate(str.substr(context), piece - context.getStart(),
149 paint, info.isRtl, pieceStartHyphen,
150 pieceEndHyphen, bc);
151 // Increment word spacing for spacer
152 if (piece.getLength() == 1 && isWordSpace(str[piece.getStart()])) {
153 bc.mAdvance += paint.wordSpacing;
154 }
155 }
156 }
157 *out = bc.mBounds;
158 }
159
160 } // namespace minikin
161