1 /*
2  * Copyright (C) 2013 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 
19 #include "minikin/Layout.h"
20 
21 #include <hb-icu.h>
22 #include <hb-ot.h>
23 #include <log/log.h>
24 #include <unicode/ubidi.h>
25 #include <unicode/utf16.h>
26 #include <utils/LruCache.h>
27 
28 #include <cmath>
29 #include <iostream>
30 #include <mutex>
31 #include <string>
32 #include <vector>
33 
34 #include "BidiUtils.h"
35 #include "LayoutSplitter.h"
36 #include "LayoutUtils.h"
37 #include "LetterSpacingUtils.h"
38 #include "LocaleListCache.h"
39 #include "MinikinInternal.h"
40 #include "minikin/Emoji.h"
41 #include "minikin/HbUtils.h"
42 #include "minikin/LayoutCache.h"
43 #include "minikin/LayoutPieces.h"
44 #include "minikin/Macros.h"
45 
46 namespace minikin {
47 
48 // TODO: Instead of two-pass letter space adjustment, apply letter space after populating from the
49 // layout cache
adjustGlyphLetterSpacingEdge(const U16StringPiece & textBuf,const MinikinPaint & paint,RunFlag runFlag,std::vector<LayoutGlyph> * glyphs)50 void adjustGlyphLetterSpacingEdge(const U16StringPiece& textBuf, const MinikinPaint& paint,
51                                   RunFlag runFlag, std::vector<LayoutGlyph>* glyphs) {
52     const float letterSpacing = paint.letterSpacing * paint.size * paint.scaleX;
53     const float letterSpacingHalf = letterSpacing * 0.5f;
54     const uint32_t glyphCount = glyphs->size();
55 
56     if (runFlag & RunFlag::LEFT_EDGE) {
57         uint32_t i;
58         for (i = 0; i < glyphCount; ++i) {
59             const uint32_t cluster = glyphs->at(i).cluster;
60             const uint32_t cp = textBuf.codePointAt(cluster);
61             if (!u_iscntrl(cp)) {
62                 break;
63             }
64         }
65 
66         for (; i < glyphCount; ++i) {
67             const uint32_t cluster = glyphs->at(i).cluster;
68             const uint32_t cp = textBuf.codePointAt(cluster);
69             if (!isLetterSpacingCapableCodePoint(cp)) {
70                 break;
71             }
72             glyphs->at(i).x -= letterSpacingHalf;
73         }
74     }
75 
76     if (runFlag & RunFlag::RIGHT_EDGE) {
77         uint32_t i;
78         for (i = 0; i < glyphCount; ++i) {
79             const uint32_t cluster = glyphs->at(glyphCount - 1 - i).cluster;
80             const uint32_t cp = textBuf.codePointAt(cluster);
81             if (!u_iscntrl(cp)) {
82                 if (!isLetterSpacingCapableCodePoint(cp)) {
83                     i = glyphCount;
84                 }
85                 break;
86             }
87         }
88 
89         if (i < glyphCount) {
90             for (uint32_t j = glyphCount - i; j < glyphCount; ++j) {
91                 glyphs->at(j).x -= letterSpacingHalf;
92             }
93         }
94     }
95 }
96 
97 // TODO: Instead of two-pass letter space adjustment, apply letter space after populating from the
98 // layout cache
adjustAdvanceLetterSpacingEdge(const U16StringPiece & textBuf,const Range & range,const BidiText & bidiText,const MinikinPaint & paint,RunFlag runFlag,float advance,float * advances)99 float adjustAdvanceLetterSpacingEdge(const U16StringPiece& textBuf, const Range& range,
100                                      const BidiText& bidiText, const MinikinPaint& paint,
101                                      RunFlag runFlag, float advance, float* advances) {
102     const float letterSpacing = paint.letterSpacing * paint.size * paint.scaleX;
103     const float letterSpacingHalf = letterSpacing * 0.5f;
104     if (letterSpacing == 0 || textBuf.length() == 0) {
105         return advance;
106     }
107     const uint32_t advOffset = range.getStart();
108 
109     if (runFlag & RunFlag::LEFT_EDGE) {
110         const BidiText::RunInfo lRun = bidiText.getRunInfoAt(0);
111         if (lRun.isRtl) {
112             uint32_t lastNonCtrlCharIndex = static_cast<uint32_t>(-1);
113             for (uint32_t i : lRun.range) {
114                 if (!u_iscntrl(textBuf.codePointAt(i)) && advances[i - advOffset] != 0) {
115                     lastNonCtrlCharIndex = i;
116                 }
117             }
118             if (lastNonCtrlCharIndex != static_cast<uint32_t>(-1)) {
119                 uint32_t cp = textBuf.codePointAt(lastNonCtrlCharIndex);
120                 if (isLetterSpacingCapableCodePoint(cp)) {
121                     advances[lastNonCtrlCharIndex - advOffset] -= letterSpacingHalf;
122                     advance -= letterSpacingHalf;
123                 }
124             }
125         } else {
126             for (uint32_t i : lRun.range) {
127                 uint32_t cp = textBuf.codePointAt(i);
128                 if (!u_iscntrl(cp) && advances[i - advOffset] != 0) {
129                     if (isLetterSpacingCapableCodePoint(cp)) {
130                         advances[i - advOffset] -= letterSpacingHalf;
131                         advance -= letterSpacingHalf;
132                     }
133                     break;
134                 }
135             }
136         }
137     }
138 
139     if (runFlag & RunFlag::RIGHT_EDGE) {
140         const BidiText::RunInfo rRun = bidiText.getRunInfoAt(bidiText.getRunCount() - 1);
141         if (rRun.isRtl) {
142             for (uint32_t i : rRun.range) {
143                 uint32_t cp = textBuf.codePointAt(i);
144                 if (!u_iscntrl(cp) && advances[i - advOffset] != 0) {
145                     if (isLetterSpacingCapableCodePoint(cp)) {
146                         advances[i - advOffset] -= letterSpacingHalf;
147                         advance -= letterSpacingHalf;
148                     }
149                     break;
150                 }
151             }
152         } else {
153             uint32_t lastNonCtrlCharIndex = static_cast<uint32_t>(-1);
154             for (uint32_t i : rRun.range) {
155                 if (!u_iscntrl(textBuf.codePointAt(i)) && advances[i - advOffset] != 0) {
156                     lastNonCtrlCharIndex = i;
157                 }
158             }
159             if (lastNonCtrlCharIndex != static_cast<uint32_t>(-1)) {
160                 uint32_t cp = textBuf.codePointAt(lastNonCtrlCharIndex);
161                 if (isLetterSpacingCapableCodePoint(cp)) {
162                     advances[lastNonCtrlCharIndex - advOffset] -= letterSpacingHalf;
163                     advance -= letterSpacingHalf;
164                 }
165             }
166         }
167     }
168     return advance;
169 }
170 
171 // TODO: Instead of two-pass letter space adjustment, apply letter space after populating from the
172 // layout cache
adjustBoundsLetterSpacingEdge(const MinikinPaint & paint,RunFlag runFlag,MinikinRect * bounds)173 void adjustBoundsLetterSpacingEdge(const MinikinPaint& paint, RunFlag runFlag,
174                                    MinikinRect* bounds) {
175     if (!bounds) {
176         return;
177     }
178     const float letterSpacing = paint.letterSpacing * paint.size * paint.scaleX;
179     const float letterSpacingHalf = letterSpacing * 0.5f;
180     if (letterSpacing == 0) {
181         return;
182     }
183     if (runFlag & RunFlag::LEFT_EDGE) {
184         bounds->mLeft -= letterSpacingHalf;
185         bounds->mRight -= letterSpacingHalf;
186     }
187 
188     if (runFlag & RunFlag::RIGHT_EDGE) {
189         bounds->mRight -= letterSpacingHalf;
190     }
191 }
192 
doLayout(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,RunFlag runFlag)193 void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
194                       const MinikinPaint& paint, StartHyphenEdit startHyphen,
195                       EndHyphenEdit endHyphen, RunFlag runFlag) {
196     const uint32_t count = range.getLength();
197     mAdvances.resize(count, 0);
198     mGlyphs.reserve(count);
199     const BidiText bidiText(textBuf, range, bidiFlags);
200     for (const BidiText::RunInfo& runInfo : bidiText) {
201         doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
202                           startHyphen, endHyphen, this, nullptr, nullptr, nullptr);
203     }
204     U16StringPiece substr = textBuf.substr(range);
205     adjustGlyphLetterSpacingEdge(substr, paint, runFlag, &mGlyphs);
206     mAdvance = adjustAdvanceLetterSpacingEdge(textBuf, range, bidiText, paint, runFlag, mAdvance,
207                                               mAdvances.data());
208 }
209 
measureText(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,float * advances,MinikinRect * bounds,uint32_t * clusterCount,RunFlag runFlag)210 float Layout::measureText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
211                           const MinikinPaint& paint, StartHyphenEdit startHyphen,
212                           EndHyphenEdit endHyphen, float* advances, MinikinRect* bounds,
213                           uint32_t* clusterCount, RunFlag runFlag) {
214     float advance = 0;
215     if (clusterCount) {
216         *clusterCount = 0;
217     }
218 
219     // To distinguish the control characters and combining character, need per character advances.
220     std::vector<float> tmpAdvances;
221     if (advances == nullptr) {
222         tmpAdvances.resize(range.getLength());
223         advances = tmpAdvances.data();
224     }
225 
226     MinikinRect tmpBounds;
227     const BidiText bidiText(textBuf, range, bidiFlags);
228     for (const BidiText::RunInfo& runInfo : bidiText) {
229         const size_t offset = range.toRangeOffset(runInfo.range.getStart());
230         float* advancesForRun = advances ? advances + offset : nullptr;
231         tmpBounds.setEmpty();
232         float run_advance = doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0,
233                                               startHyphen, endHyphen, nullptr, advancesForRun,
234                                               bounds ? &tmpBounds : nullptr, clusterCount);
235         if (bounds) {
236             bounds->join(tmpBounds, advance, 0);
237         }
238         advance += run_advance;
239     }
240     adjustBoundsLetterSpacingEdge(paint, runFlag, bounds);
241     return adjustAdvanceLetterSpacingEdge(textBuf, range, bidiText, paint, runFlag, advance,
242                                           advances);
243 }
244 
doLayoutRunCached(const U16StringPiece & textBuf,const Range & range,bool isRtl,const MinikinPaint & paint,size_t dstStart,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,Layout * layout,float * advances,MinikinRect * bounds,uint32_t * clusterCount)245 float Layout::doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
246                                 const MinikinPaint& paint, size_t dstStart,
247                                 StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
248                                 Layout* layout, float* advances, MinikinRect* bounds,
249                                 uint32_t* clusterCount) {
250     if (!range.isValid()) {
251         return 0.0f;  // ICU failed to retrieve the bidi run?
252     }
253     float advance = 0;
254     MinikinRect tmpBounds;
255     for (const auto[context, piece] : LayoutSplitter(textBuf, range, isRtl)) {
256         // Hyphenation only applies to the start/end of run.
257         const StartHyphenEdit pieceStartHyphen =
258                 (piece.getStart() == range.getStart()) ? startHyphen : StartHyphenEdit::NO_EDIT;
259         const EndHyphenEdit pieceEndHyphen =
260                 (piece.getEnd() == range.getEnd()) ? endHyphen : EndHyphenEdit::NO_EDIT;
261         float* advancesForRun =
262                 advances ? advances + (piece.getStart() - range.getStart()) : nullptr;
263         tmpBounds.setEmpty();
264         float word_advance = doLayoutWord(
265                 textBuf.data() + context.getStart(), piece.getStart() - context.getStart(),
266                 piece.getLength(), context.getLength(), isRtl, paint, piece.getStart() - dstStart,
267                 pieceStartHyphen, pieceEndHyphen, layout, advancesForRun,
268                 bounds ? &tmpBounds : nullptr, clusterCount);
269         if (bounds) {
270             bounds->join(tmpBounds, advance, 0);
271         }
272         advance += word_advance;
273     }
274     return advance;
275 }
276 
277 class LayoutAppendFunctor {
278 public:
LayoutAppendFunctor(Layout * layout,float * advances,uint32_t outOffset,float wordSpacing,MinikinRect * bounds)279     LayoutAppendFunctor(Layout* layout, float* advances, uint32_t outOffset, float wordSpacing,
280                         MinikinRect* bounds)
281             : mLayout(layout),
282               mAdvances(advances),
283               mOutOffset(outOffset),
284               mWordSpacing(wordSpacing),
285               mBounds(bounds) {}
286 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &,const MinikinRect & bounds)287     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
288                     const MinikinRect& bounds) {
289         if (mLayout) {
290             mLayout->appendLayout(layoutPiece, mOutOffset, mWordSpacing);
291         }
292         if (mAdvances) {
293             const std::vector<float>& advances = layoutPiece.advances();
294             std::copy(advances.begin(), advances.end(), mAdvances);
295         }
296         if (mBounds) {
297             mBounds->join(bounds, mTotalAdvance, 0);
298         }
299         mTotalAdvance += layoutPiece.advance();
300         mClusterCount += layoutPiece.clusterCount();
301     }
302 
getTotalAdvance()303     float getTotalAdvance() { return mTotalAdvance; }
getClusterCount() const304     uint32_t getClusterCount() const { return mClusterCount; }
305 
306 private:
307     Layout* mLayout;
308     float* mAdvances;
309     float mTotalAdvance{0};
310     const uint32_t mOutOffset;
311     const float mWordSpacing;
312     uint32_t mClusterCount{0};
313     MinikinRect* mBounds;
314 };
315 
doLayoutWord(const uint16_t * buf,size_t start,size_t count,size_t bufSize,bool isRtl,const MinikinPaint & paint,size_t bufStart,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,Layout * layout,float * advances,MinikinRect * bounds,uint32_t * clusterCount)316 float Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
317                            bool isRtl, const MinikinPaint& paint, size_t bufStart,
318                            StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, Layout* layout,
319                            float* advances, MinikinRect* bounds, uint32_t* clusterCount) {
320     float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
321     float totalAdvance = 0;
322     const bool boundsCalculation = bounds != nullptr;
323 
324     const U16StringPiece textBuf(buf, bufSize);
325     const Range range(start, start + count);
326     LayoutAppendFunctor f(layout, advances, bufStart, wordSpacing, bounds);
327     LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen,
328                                            boundsCalculation, f);
329     totalAdvance = f.getTotalAdvance();
330     if (clusterCount) {
331         *clusterCount += f.getClusterCount();
332     }
333 
334     if (wordSpacing != 0) {
335         totalAdvance += wordSpacing;
336         if (advances) {
337             advances[0] += wordSpacing;
338         }
339     }
340     return totalAdvance;
341 }
342 
appendLayout(const LayoutPiece & src,size_t start,float extraAdvance)343 void Layout::appendLayout(const LayoutPiece& src, size_t start, float extraAdvance) {
344     for (size_t i = 0; i < src.glyphCount(); i++) {
345         mGlyphs.emplace_back(src.fontAt(i), src.glyphIdAt(i), src.clusterAt(i) + start,
346                              mAdvance + src.pointAt(i).x, src.pointAt(i).y);
347     }
348     const std::vector<float>& advances = src.advances();
349     for (size_t i = 0; i < advances.size(); i++) {
350         mAdvances[i + start] = advances[i];
351         if (i == 0) {
352             mAdvances[start] += extraAdvance;
353         }
354     }
355     mAdvance += src.advance() + extraAdvance;
356 }
357 
purgeCaches()358 void Layout::purgeCaches() {
359     LayoutCache::getInstance().clear();
360 }
361 
362 }  // namespace minikin
363