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