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 & paint)216     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
217         MinikinRect pieceBounds;
218         MinikinRect tmpRect;
219         for (uint32_t i = 0; i < layoutPiece.glyphCount(); ++i) {
220             const FakedFont& font = layoutPiece.fontAt(i);
221             const Point& point = layoutPiece.pointAt(i);
222 
223             MinikinFont* minikinFont = font.font->typeface().get();
224             minikinFont->GetBounds(&tmpRect, layoutPiece.glyphIdAt(i), paint, font.fakery);
225             tmpRect.offset(point.x, point.y);
226             pieceBounds.join(tmpRect);
227         }
228         pieceBounds.offset(mAdvance, 0);
229         mBounds.join(pieceBounds);
230         mAdvance += layoutPiece.advance();
231     }
232 
bounds() const233     const MinikinRect& bounds() const { return mBounds; }
advance() const234     float advance() const { return mAdvance; }
235 
236 private:
237     float mAdvance;
238     MinikinRect mBounds;
239 };
240 
getBounds(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const241 std::pair<float, MinikinRect> StyleRun::getBounds(const U16StringPiece& textBuf, const Range& range,
242                                                   const LayoutPieces& pieces) const {
243     BoundsCompositor compositor;
244     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
245     const uint32_t paintId = pieces.findPaintId(mPaint);
246     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
247         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
248             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
249                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
250                                compositor);
251         }
252     }
253     return std::make_pair(compositor.advance(), compositor.bounds());
254 }
255 
256 // Helper class for composing total extent.
257 class ExtentCompositor {
258 public:
ExtentCompositor()259     ExtentCompositor() {}
260 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)261     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
262         mExtent.extendBy(layoutPiece.extent());
263     }
264 
extent() const265     const MinikinExtent& extent() const { return mExtent; }
266 
267 private:
268     MinikinExtent mExtent;
269 };
270 
getExtent(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const271 MinikinExtent StyleRun::getExtent(const U16StringPiece& textBuf, const Range& range,
272                                   const LayoutPieces& pieces) const {
273     ExtentCompositor compositor;
274     Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
275     const uint32_t paintId = pieces.findPaintId(mPaint);
276     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
277         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
278             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
279                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
280                                compositor);
281         }
282     }
283     return compositor.extent();
284 }
285 
buildLayout(const U16StringPiece & textBuf,const Range & range,const Range & contextRange,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)286 Layout MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
287                                  const Range& contextRange, const MinikinPaint& paint,
288                                  StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) {
289     Layout outLayout(range.getLength());
290     for (const auto& run : runs) {
291         const Range& runRange = run->getRange();
292         if (!Range::intersects(range, runRange)) {
293             continue;
294         }
295         const Range targetRange = Range::intersection(runRange, range);
296         StartHyphenEdit startEdit =
297                 targetRange.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
298         EndHyphenEdit endEdit =
299                 targetRange.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
300         run->appendLayout(textBuf, targetRange, contextRange, layoutPieces, paint, range.getStart(),
301                           startEdit, endEdit, &outLayout);
302     }
303     return outLayout;
304 }
305 
getBounds(const U16StringPiece & textBuf,const Range & range) const306 MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) const {
307     MinikinRect rect;
308     float totalAdvance = 0.0f;
309 
310     for (const auto& run : runs) {
311         const Range& runRange = run->getRange();
312         if (!Range::intersects(range, runRange)) {
313             continue;
314         }
315         auto[advance, bounds] =
316                 run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
317         bounds.offset(totalAdvance, 0);
318         rect.join(bounds);
319         totalAdvance += advance;
320     }
321     return rect;
322 }
323 
getExtent(const U16StringPiece & textBuf,const Range & range) const324 MinikinExtent MeasuredText::getExtent(const U16StringPiece& textBuf, const Range& range) const {
325     MinikinExtent extent;
326     for (const auto& run : runs) {
327         const Range& runRange = run->getRange();
328         if (!Range::intersects(range, runRange)) {
329             continue;
330         }
331         MinikinExtent runExtent =
332                 run->getExtent(textBuf, Range::intersection(runRange, range), layoutPieces);
333         extent.extendBy(runExtent);
334     }
335     return extent;
336 }
337 
338 }  // namespace minikin
339