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/GraphemeBreak.h"
25 #include "minikin/LayoutCache.h"
26
27 namespace {
isAsciiOrBidiControlCharacter(uint16_t c)28 bool isAsciiOrBidiControlCharacter(uint16_t c) {
29 return (0x0000 <= c && c <= 0x001F) // ASCII control characters
30 || c == 0x061C || c == 0x200E || c == 0x200F // BiDi control characters
31 || (0x202A <= c && c <= 0x202E) || (0x2066 <= c && c <= 0x2069);
32 }
33
34 } // namespace
35
36 namespace minikin {
37
38 // These could be considered helper methods of layout, but need only be loosely coupled, so
39 // are separate.
40
41 /**
42 * Return the unsigned advance of the given offset from the run start.
43 *
44 * @param advances the computed advances of the characters in buf. The advance of
45 * the i-th character in buf is stored at index (i - layoutStart) in this array.
46 * @param buf the text stored in utf-16 format.
47 * @param layoutStart the start index of the character that is laid out.
48 * @param start the start index of the run.
49 * @param count the number of the characters in this run.
50 * @param offset the target offset to compute the index. It should be in the
51 * range of [start, start + count).
52 * @return the unsigned advance from the run start to the given offset.
53 */
getRunAdvance(const float * advances,const uint16_t * buf,size_t layoutStart,size_t start,size_t count,size_t offset)54 static float getRunAdvance(const float* advances, const uint16_t* buf, size_t layoutStart,
55 size_t start, size_t count, size_t offset) {
56 float advance = 0.0f;
57 size_t lastCluster = start;
58 float clusterWidth = 0.0f;
59 for (size_t i = start; i < offset; i++) {
60 float charAdvance = advances[i - layoutStart];
61 if (charAdvance != 0.0f) {
62 advance += charAdvance;
63 lastCluster = i;
64 clusterWidth = charAdvance;
65 }
66 }
67 if (offset < start + count && !isAsciiOrBidiControlCharacter(buf[offset]) &&
68 advances[offset - layoutStart] == 0.0f) {
69 // In the middle of a cluster, distribute width of cluster so that each grapheme cluster
70 // gets an equal share.
71 // TODO: get caret information out of font when that's available
72 size_t nextCluster;
73 for (nextCluster = offset + 1; nextCluster < start + count; nextCluster++) {
74 if (advances[nextCluster - layoutStart] != 0.0f ||
75 isAsciiOrBidiControlCharacter(buf[nextCluster])) {
76 break;
77 }
78 }
79 int numGraphemeClusters = 0;
80 int numGraphemeClustersAfter = 0;
81 for (size_t i = lastCluster; i < nextCluster; i++) {
82 bool isAfter = i >= offset;
83 if (GraphemeBreak::isGraphemeBreak(advances + (start - layoutStart), buf, start, count,
84 i)) {
85 numGraphemeClusters++;
86 if (isAfter) {
87 numGraphemeClustersAfter++;
88 }
89 }
90 }
91 if (numGraphemeClusters > 0) {
92 advance -= clusterWidth * numGraphemeClustersAfter / numGraphemeClusters;
93 }
94 }
95 return advance;
96 }
97
98 /**
99 * Helper method that distribute the advance to ligature characters.
100 * When ligature is applied, the first character in the ligature is assigned with the entire width.
101 * This method will evenly distribute the advance to each grapheme in the ligature.
102 *
103 * @param advances the computed advances of the characters in buf. The advance of
104 * the i-th character in buf is stored at index (i - start) in this array. This
105 * method will update this array so that advances is distributed evenly for
106 * ligature characters.
107 * @param buf the text stored in utf-16 format.
108 * @param start the start index of the run.
109 * @param count the number of the characters in this run.
110 */
distributeAdvances(float * advances,const uint16_t * buf,size_t start,size_t count)111 void distributeAdvances(float* advances, const uint16_t* buf, size_t start, size_t count) {
112 size_t clusterStart = start;
113 while (clusterStart < start + count) {
114 float clusterAdvance = advances[clusterStart - start];
115 size_t clusterEnd;
116 for (clusterEnd = clusterStart + 1; clusterEnd < start + count; clusterEnd++) {
117 if (advances[clusterEnd - start] != 0.0f ||
118 isAsciiOrBidiControlCharacter(buf[clusterEnd])) {
119 break;
120 }
121 }
122 size_t numGraphemeClusters = 0;
123 for (size_t i = clusterStart; i < clusterEnd; i++) {
124 if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
125 numGraphemeClusters++;
126 }
127 }
128 // When there are more than one grapheme in this cluster, ligature is applied.
129 // And we will distribute the width to each grapheme.
130 if (numGraphemeClusters > 1) {
131 for (size_t i = clusterStart; i < clusterEnd; ++i) {
132 if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
133 // Only distribute the advance to the first character of the cluster.
134 advances[i - start] = clusterAdvance / numGraphemeClusters;
135 }
136 }
137 }
138 clusterStart = clusterEnd;
139 }
140 }
141
getRunAdvance(const float * advances,const uint16_t * buf,size_t start,size_t count,size_t offset)142 float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
143 size_t offset) {
144 return getRunAdvance(advances, buf, start, start, count, offset);
145 }
146
147 /**
148 * Essentially the inverse of getRunAdvance. Compute the value of offset for which the
149 * measured caret comes closest to the provided advance param, and which is on a grapheme
150 * cluster boundary.
151 *
152 * The actual implementation fast-forwards through clusters to get "close", then does a finer-grain
153 * search within the cluster and grapheme breaks.
154 */
getOffsetForAdvance(const float * advances,const uint16_t * buf,size_t start,size_t count,float advance)155 size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count,
156 float advance) {
157 float x = 0.0f, xLastClusterStart = 0.0f, xSearchStart = 0.0f;
158 size_t lastClusterStart = start, searchStart = start;
159 size_t max = start + count;
160 for (size_t i = start; i < max; i++) {
161 if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
162 searchStart = lastClusterStart;
163 xSearchStart = xLastClusterStart;
164 }
165 float width = advances[i - start];
166 if (width != 0.0f) {
167 lastClusterStart = i;
168 xLastClusterStart = x;
169 x += width;
170 if (x > advance) {
171 break;
172 }
173 }
174 }
175 size_t best = searchStart;
176 float bestDist = FLT_MAX;
177 for (size_t i = searchStart; i <= max; i++) {
178 if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
179 // "getRunAdvance(layout, buf, start, count, i) - advance" but more efficient
180 float delta = getRunAdvance(advances, buf, start, searchStart, max - searchStart, i)
181
182 + xSearchStart - advance;
183 if (std::abs(delta) < bestDist) {
184 bestDist = std::abs(delta);
185 best = i;
186 }
187 if (delta >= 0.0f) {
188 break;
189 }
190 }
191 }
192 return best;
193 }
194
195 struct BoundsComposer {
BoundsComposerminikin::BoundsComposer196 BoundsComposer() : mAdvance(0) {}
197
operator ()minikin::BoundsComposer198 void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
199 const MinikinRect& bounds) {
200 mBounds.join(bounds, mAdvance, 0);
201 mAdvance += layoutPiece.advance();
202 }
203
204 float mAdvance;
205 MinikinRect mBounds;
206 };
207
getBounds(const U16StringPiece & str,const Range & range,Bidi bidiFlag,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,MinikinRect * out)208 void getBounds(const U16StringPiece& str, const Range& range, Bidi bidiFlag,
209 const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
210 MinikinRect* out) {
211 BoundsComposer bc;
212 for (const BidiText::RunInfo info : BidiText(str, range, bidiFlag)) {
213 for (const auto [context, piece] : LayoutSplitter(str, info.range, info.isRtl)) {
214 const StartHyphenEdit pieceStartHyphen =
215 (piece.getStart() == range.getStart()) ? startHyphen : StartHyphenEdit::NO_EDIT;
216 const EndHyphenEdit pieceEndHyphen =
217 (piece.getEnd() == range.getEnd()) ? endHyphen : EndHyphenEdit::NO_EDIT;
218 LayoutCache::getInstance().getOrCreate(
219 str.substr(context), piece - context.getStart(), paint, info.isRtl,
220 pieceStartHyphen, pieceEndHyphen, true /* bounds calculation */, bc);
221 // Increment word spacing for spacer
222 if (piece.getLength() == 1 && isWordSpace(str[piece.getStart()])) {
223 bc.mAdvance += paint.wordSpacing;
224 }
225 }
226 }
227 *out = bc.mBounds;
228 }
229
230 struct ExtentComposer {
ExtentComposerminikin::ExtentComposer231 ExtentComposer() {}
232
operator ()minikin::ExtentComposer233 void operator()(const LayoutPiece& layoutPiece, const MinikinPaint&, const MinikinRect&) {
234 extent.extendBy(layoutPiece.extent());
235 }
236
237 MinikinExtent extent;
238 };
239
getFontExtent(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlag,const MinikinPaint & paint)240 MinikinExtent getFontExtent(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlag,
241 const MinikinPaint& paint) {
242 ExtentComposer composer;
243 for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
244 for (const auto [context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
245 LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
246 piece - context.getStart(), paint, info.isRtl,
247 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT,
248 false /* bounds calculation */, composer);
249 }
250 }
251 return composer.extent;
252 }
253
254 } // namespace minikin
255