• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
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     for (size_t i = start; i < start + count; i++) {
160         if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
161             searchStart = lastClusterStart;
162             xSearchStart = xLastClusterStart;
163         }
164         float width = advances[i - start];
165         if (width != 0.0f) {
166             lastClusterStart = i;
167             xLastClusterStart = x;
168             x += width;
169             if (x > advance) {
170                 break;
171             }
172         }
173     }
174     size_t best = searchStart;
175     float bestDist = FLT_MAX;
176     for (size_t i = searchStart; i <= start + count; i++) {
177         if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {
178             // "getRunAdvance(layout, buf, start, count, i) - advance" but more efficient
179             float delta = getRunAdvance(advances, buf, start, searchStart, count - searchStart, i)
180 
181                           + xSearchStart - advance;
182             if (std::abs(delta) < bestDist) {
183                 bestDist = std::abs(delta);
184                 best = i;
185             }
186             if (delta >= 0.0f) {
187                 break;
188             }
189         }
190     }
191     return best;
192 }
193 
194 struct BoundsComposer {
BoundsComposerminikin::BoundsComposer195     BoundsComposer() : mAdvance(0) {}
196 
operator ()minikin::BoundsComposer197     void operator()(const MinikinRect& rect, float advance) {
198         MinikinRect tmp = rect;
199         tmp.offset(mAdvance, 0);
200         mBounds.join(tmp);
201         mAdvance += 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             BoundsCache::getInstance().getOrCreate(str.substr(context), piece - context.getStart(),
219                                                    paint, info.isRtl, pieceStartHyphen,
220                                                    pieceEndHyphen, 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&) {
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(
246                     textBuf.substr(context), piece - context.getStart(), paint, info.isRtl,
247                     StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, composer);
248         }
249     }
250     return composer.extent;
251 }
252 
253 }  // namespace minikin
254