1 /*
2  * Copyright (C) 2017 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 #include "minikin/MeasuredText.h"
19 
20 #include "minikin/Layout.h"
21 
22 #include "BidiUtils.h"
23 #include "LayoutSplitter.h"
24 #include "LayoutUtils.h"
25 #include "LineBreakerUtil.h"
26 
27 namespace minikin {
28 
29 // Helper class for composing character advances.
30 class AdvancesCompositor {
31 public:
AdvancesCompositor(std::vector<float> * outAdvances,LayoutPieces * outPieces)32     AdvancesCompositor(std::vector<float>* outAdvances, LayoutPieces* outPieces)
33             : mOutAdvances(outAdvances), mOutPieces(outPieces) {}
34 
setNextRange(const Range & range,bool dir)35     void setNextRange(const Range& range, bool dir) {
36         mRange = range;
37         mDir = dir;
38     }
39 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint & paint)40     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
41         const std::vector<float>& advances = layoutPiece.advances();
42         std::copy(advances.begin(), advances.end(), mOutAdvances->begin() + mRange.getStart());
43 
44         if (mOutPieces != nullptr) {
45             mOutPieces->insert(mRange, 0 /* no edit */, layoutPiece, mDir, paint);
46         }
47     }
48 
49 private:
50     Range mRange;
51     bool mDir;
52     std::vector<float>* mOutAdvances;
53     LayoutPieces* mOutPieces;
54 };
55 
getMetrics(const U16StringPiece & textBuf,std::vector<float> * advances,LayoutPieces * precomputed,LayoutPieces * outPieces) const56 void StyleRun::getMetrics(const U16StringPiece& textBuf, std::vector<float>* advances,
57                           LayoutPieces* precomputed, LayoutPieces* outPieces) const {
58     AdvancesCompositor compositor(advances, outPieces);
59     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
60     const uint32_t paintId =
61             (precomputed == nullptr) ? LayoutPieces::kNoPaintId : precomputed->findPaintId(mPaint);
62     for (const BidiText::RunInfo info : BidiText(textBuf, mRange, bidiFlag)) {
63         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
64             compositor.setNextRange(piece, info.isRtl);
65             if (paintId == LayoutPieces::kNoPaintId) {
66                 LayoutCache::getInstance().getOrCreate(
67                         textBuf.substr(context), piece - context.getStart(), mPaint, info.isRtl,
68                         StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, compositor);
69             } else {
70                 precomputed->getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
71                                          StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
72                                          compositor);
73             }
74         }
75     }
76 }
77 
78 // Helper class for composing total amount of advance
79 class TotalAdvanceCompositor {
80 public:
TotalAdvanceCompositor(LayoutPieces * outPieces)81     TotalAdvanceCompositor(LayoutPieces* outPieces) : mTotalAdvance(0), mOutPieces(outPieces) {}
82 
setNextContext(const Range & range,HyphenEdit edit,bool dir)83     void setNextContext(const Range& range, HyphenEdit edit, bool dir) {
84         mRange = range;
85         mEdit = edit;
86         mDir = dir;
87     }
88 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint & paint)89     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
90         mTotalAdvance += layoutPiece.advance();
91         if (mOutPieces != nullptr) {
92             mOutPieces->insert(mRange, mEdit, layoutPiece, mDir, paint);
93         }
94     }
95 
advance() const96     float advance() const { return mTotalAdvance; }
97 
98 private:
99     float mTotalAdvance;
100     Range mRange;
101     HyphenEdit mEdit;
102     bool mDir;
103     LayoutPieces* mOutPieces;
104 };
105 
measureHyphenPiece(const U16StringPiece & textBuf,const Range & range,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,LayoutPieces * pieces) const106 float StyleRun::measureHyphenPiece(const U16StringPiece& textBuf, const Range& range,
107                                    StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
108                                    LayoutPieces* pieces) const {
109     TotalAdvanceCompositor compositor(pieces);
110     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
111     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
112         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
113             const StartHyphenEdit startEdit =
114                     piece.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
115             const EndHyphenEdit endEdit =
116                     piece.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
117 
118             compositor.setNextContext(piece, packHyphenEdit(startEdit, endEdit), info.isRtl);
119             LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
120                                                    piece - context.getStart(), mPaint, info.isRtl,
121                                                    startEdit, endEdit, compositor);
122         }
123     }
124     return compositor.advance();
125 }
126 
measure(const U16StringPiece & textBuf,bool computeHyphenation,bool computeLayout,MeasuredText * hint)127 void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenation,
128                            bool computeLayout, MeasuredText* hint) {
129     if (textBuf.size() == 0) {
130         return;
131     }
132 
133     LayoutPieces* piecesOut = computeLayout ? &layoutPieces : nullptr;
134     CharProcessor proc(textBuf);
135     for (const auto& run : runs) {
136         const Range& range = run->getRange();
137         run->getMetrics(textBuf, &widths, hint ? &hint->layoutPieces : nullptr, piecesOut);
138 
139         if (!computeHyphenation || !run->canBreak()) {
140             continue;
141         }
142 
143         proc.updateLocaleIfNecessary(*run);
144         for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
145             // Even if the run is not a candidate of line break, treat the end of run as the line
146             // break candidate.
147             const bool canBreak = run->canBreak() || (i + 1) == range.getEnd();
148             proc.feedChar(i, textBuf[i], widths[i], canBreak);
149 
150             const uint32_t nextCharOffset = i + 1;
151             if (nextCharOffset != proc.nextWordBreak) {
152                 continue;  // Wait until word break point.
153             }
154 
155             populateHyphenationPoints(textBuf, *run, *proc.hyphenator, proc.contextRange(),
156                                       proc.wordRange(), &hyphenBreaks, piecesOut);
157         }
158     }
159 }
160 
161 // Helper class for composing Layout object.
162 class LayoutCompositor {
163 public:
LayoutCompositor(Layout * outLayout,float extraAdvance)164     LayoutCompositor(Layout* outLayout, float extraAdvance)
165             : mOutLayout(outLayout), mExtraAdvance(extraAdvance) {}
166 
setOutOffset(uint32_t outOffset)167     void setOutOffset(uint32_t outOffset) { mOutOffset = outOffset; }
168 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)169     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
170         mOutLayout->appendLayout(layoutPiece, mOutOffset, mExtraAdvance);
171     }
172 
173     uint32_t mOutOffset;
174     Layout* mOutLayout;
175     float mExtraAdvance;
176 };
177 
appendLayout(const U16StringPiece & textBuf,const Range & range,const Range &,const LayoutPieces & pieces,const MinikinPaint & paint,uint32_t outOrigin,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,Layout * outLayout) const178 void StyleRun::appendLayout(const U16StringPiece& textBuf, const Range& range,
179                             const Range& /* context */, const LayoutPieces& pieces,
180                             const MinikinPaint& paint, uint32_t outOrigin,
181                             StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
182                             Layout* outLayout) const {
183     float wordSpacing = range.getLength() == 1 && isWordSpace(textBuf[range.getStart()])
184                                 ? mPaint.wordSpacing
185                                 : 0;
186     bool canUsePrecomputedResult = mPaint == paint;
187 
188     LayoutCompositor compositor(outLayout, wordSpacing);
189     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
190     const uint32_t paintId = pieces.findPaintId(mPaint);
191     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
192         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
193             compositor.setOutOffset(piece.getStart() - outOrigin);
194             const StartHyphenEdit startEdit =
195                     range.getStart() == piece.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
196             const EndHyphenEdit endEdit =
197                     range.getEnd() == piece.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
198 
199             if (canUsePrecomputedResult) {
200                 pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl, startEdit, endEdit,
201                                    paintId, compositor);
202             } else {
203                 LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
204                                                        piece - context.getStart(), paint,
205                                                        info.isRtl, startEdit, endEdit, compositor);
206             }
207         }
208     }
209 }
210 
211 // Helper class for composing bounding box.
212 class BoundsCompositor {
213 public:
BoundsCompositor()214     BoundsCompositor() : mAdvance(0) {}
215 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)216     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
217         MinikinRect tmpBounds = layoutPiece.bounds();
218         tmpBounds.offset(mAdvance, 0);
219         mBounds.join(tmpBounds);
220         mAdvance += layoutPiece.advance();
221     }
222 
bounds() const223     const MinikinRect& bounds() const { return mBounds; }
advance() const224     float advance() const { return mAdvance; }
225 
226 private:
227     float mAdvance;
228     MinikinRect mBounds;
229 };
230 
getBounds(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const231 std::pair<float, MinikinRect> StyleRun::getBounds(const U16StringPiece& textBuf, const Range& range,
232                                                   const LayoutPieces& pieces) const {
233     BoundsCompositor compositor;
234     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
235     const uint32_t paintId = pieces.findPaintId(mPaint);
236     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
237         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
238             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
239                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
240                                compositor);
241         }
242     }
243     return std::make_pair(compositor.advance(), compositor.bounds());
244 }
245 
246 // Helper class for composing total extent.
247 class ExtentCompositor {
248 public:
ExtentCompositor()249     ExtentCompositor() {}
250 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)251     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
252         mExtent.extendBy(layoutPiece.extent());
253     }
254 
extent() const255     const MinikinExtent& extent() const { return mExtent; }
256 
257 private:
258     MinikinExtent mExtent;
259 };
260 
getExtent(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const261 MinikinExtent StyleRun::getExtent(const U16StringPiece& textBuf, const Range& range,
262                                   const LayoutPieces& pieces) const {
263     ExtentCompositor compositor;
264     Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
265     const uint32_t paintId = pieces.findPaintId(mPaint);
266     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
267         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
268             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
269                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
270                                compositor);
271         }
272     }
273     return compositor.extent();
274 }
275 
buildLayout(const U16StringPiece & textBuf,const Range & range,const Range & contextRange,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)276 Layout MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
277                                  const Range& contextRange, const MinikinPaint& paint,
278                                  StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) {
279     Layout outLayout(range.getLength());
280     for (const auto& run : runs) {
281         const Range& runRange = run->getRange();
282         if (!Range::intersects(range, runRange)) {
283             continue;
284         }
285         const Range targetRange = Range::intersection(runRange, range);
286         StartHyphenEdit startEdit =
287                 targetRange.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
288         EndHyphenEdit endEdit =
289                 targetRange.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
290         run->appendLayout(textBuf, targetRange, contextRange, layoutPieces, paint, range.getStart(),
291                           startEdit, endEdit, &outLayout);
292     }
293     return outLayout;
294 }
295 
getBounds(const U16StringPiece & textBuf,const Range & range) const296 MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) const {
297     MinikinRect rect;
298     float totalAdvance = 0.0f;
299 
300     for (const auto& run : runs) {
301         const Range& runRange = run->getRange();
302         if (!Range::intersects(range, runRange)) {
303             continue;
304         }
305         auto[advance, bounds] =
306                 run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
307         bounds.offset(totalAdvance, 0);
308         rect.join(bounds);
309         totalAdvance += advance;
310     }
311     return rect;
312 }
313 
getExtent(const U16StringPiece & textBuf,const Range & range) const314 MinikinExtent MeasuredText::getExtent(const U16StringPiece& textBuf, const Range& range) const {
315     MinikinExtent extent;
316     for (const auto& run : runs) {
317         const Range& runRange = run->getRange();
318         if (!Range::intersects(range, runRange)) {
319             continue;
320         }
321         MinikinExtent runExtent =
322                 run->getExtent(textBuf, Range::intersection(runRange, range), layoutPieces);
323         extent.extendBy(runExtent);
324     }
325     return extent;
326 }
327 
328 }  // namespace minikin
329