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