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 <cmath>
22 #include <iostream>
23 #include <mutex>
24 #include <string>
25 #include <vector>
26 
27 #include <hb-icu.h>
28 #include <hb-ot.h>
29 #include <log/log.h>
30 #include <unicode/ubidi.h>
31 #include <unicode/utf16.h>
32 #include <utils/LruCache.h>
33 
34 #include "minikin/Emoji.h"
35 #include "minikin/HbUtils.h"
36 #include "minikin/LayoutCache.h"
37 #include "minikin/LayoutPieces.h"
38 #include "minikin/Macros.h"
39 
40 #include "BidiUtils.h"
41 #include "LayoutSplitter.h"
42 #include "LayoutUtils.h"
43 #include "LocaleListCache.h"
44 #include "MinikinInternal.h"
45 
46 namespace minikin {
47 
doLayout(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)48 void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
49                       const MinikinPaint& paint, StartHyphenEdit startHyphen,
50                       EndHyphenEdit endHyphen) {
51     const uint32_t count = range.getLength();
52     mAdvances.resize(count, 0);
53     mGlyphs.reserve(count);
54     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
55         doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
56                           startHyphen, endHyphen, this, nullptr);
57     }
58 }
59 
measureText(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,float * advances)60 float Layout::measureText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
61                           const MinikinPaint& paint, StartHyphenEdit startHyphen,
62                           EndHyphenEdit endHyphen, float* advances) {
63     float advance = 0;
64     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
65         const size_t offset = range.toRangeOffset(runInfo.range.getStart());
66         float* advancesForRun = advances ? advances + offset : nullptr;
67         advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0, startHyphen,
68                                      endHyphen, nullptr, advancesForRun);
69     }
70     return advance;
71 }
72 
doLayoutRunCached(const U16StringPiece & textBuf,const Range & range,bool isRtl,const MinikinPaint & paint,size_t dstStart,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,Layout * layout,float * advances)73 float Layout::doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
74                                 const MinikinPaint& paint, size_t dstStart,
75                                 StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
76                                 Layout* layout, float* advances) {
77     if (!range.isValid()) {
78         return 0.0f;  // ICU failed to retrieve the bidi run?
79     }
80     float advance = 0;
81     for (const auto[context, piece] : LayoutSplitter(textBuf, range, isRtl)) {
82         // Hyphenation only applies to the start/end of run.
83         const StartHyphenEdit pieceStartHyphen =
84                 (piece.getStart() == range.getStart()) ? startHyphen : StartHyphenEdit::NO_EDIT;
85         const EndHyphenEdit pieceEndHyphen =
86                 (piece.getEnd() == range.getEnd()) ? endHyphen : EndHyphenEdit::NO_EDIT;
87         float* advancesForRun =
88                 advances ? advances + (piece.getStart() - range.getStart()) : nullptr;
89         advance += doLayoutWord(textBuf.data() + context.getStart(),
90                                 piece.getStart() - context.getStart(), piece.getLength(),
91                                 context.getLength(), isRtl, paint, piece.getStart() - dstStart,
92                                 pieceStartHyphen, pieceEndHyphen, layout, advancesForRun);
93     }
94     return advance;
95 }
96 
97 class LayoutAppendFunctor {
98 public:
LayoutAppendFunctor(Layout * layout,float * advances,float * totalAdvance,uint32_t outOffset,float wordSpacing)99     LayoutAppendFunctor(Layout* layout, float* advances, float* totalAdvance, uint32_t outOffset,
100                         float wordSpacing)
101             : mLayout(layout),
102               mAdvances(advances),
103               mTotalAdvance(totalAdvance),
104               mOutOffset(outOffset),
105               mWordSpacing(wordSpacing) {}
106 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)107     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
108         if (mLayout) {
109             mLayout->appendLayout(layoutPiece, mOutOffset, mWordSpacing);
110         }
111         if (mAdvances) {
112             const std::vector<float>& advances = layoutPiece.advances();
113             std::copy(advances.begin(), advances.end(), mAdvances);
114         }
115         if (mTotalAdvance) {
116             *mTotalAdvance = layoutPiece.advance();
117         }
118     }
119 
120 private:
121     Layout* mLayout;
122     float* mAdvances;
123     float* mTotalAdvance;
124     const uint32_t mOutOffset;
125     const float mWordSpacing;
126 };
127 
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)128 float Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
129                            bool isRtl, const MinikinPaint& paint, size_t bufStart,
130                            StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, Layout* layout,
131                            float* advances) {
132     float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
133     float totalAdvance = 0;
134 
135     const U16StringPiece textBuf(buf, bufSize);
136     const Range range(start, start + count);
137     LayoutAppendFunctor f(layout, advances, &totalAdvance, bufStart, wordSpacing);
138     LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen, f);
139 
140     if (wordSpacing != 0) {
141         totalAdvance += wordSpacing;
142         if (advances) {
143             advances[0] += wordSpacing;
144         }
145     }
146     return totalAdvance;
147 }
148 
appendLayout(const LayoutPiece & src,size_t start,float extraAdvance)149 void Layout::appendLayout(const LayoutPiece& src, size_t start, float extraAdvance) {
150     for (size_t i = 0; i < src.glyphCount(); i++) {
151         mGlyphs.emplace_back(src.fontAt(i), src.glyphIdAt(i), mAdvance + src.pointAt(i).x,
152                              src.pointAt(i).y);
153     }
154     const std::vector<float>& advances = src.advances();
155     for (size_t i = 0; i < advances.size(); i++) {
156         mAdvances[i + start] = advances[i];
157         if (i == 0) {
158             mAdvances[start] += extraAdvance;
159         }
160     }
161     MinikinRect srcBounds(src.bounds());
162     srcBounds.offset(mAdvance, 0);
163     mBounds.join(srcBounds);
164     mAdvance += src.advance() + extraAdvance;
165 }
166 
purgeCaches()167 void Layout::purgeCaches() {
168     LayoutCache::getInstance().clear();
169 }
170 
171 }  // namespace minikin
172