• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 advances.
79 class TotalAdvancesCompositor {
80 public:
TotalAdvancesCompositor()81     TotalAdvancesCompositor() : mOut(0) {}
82 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)83     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint&) {
84         for (float w : layoutPiece.advances()) {
85             mOut += w;
86         }
87     }
88 
getTotalAdvance()89     float getTotalAdvance() { return mOut; }
90 
91 private:
92     float mOut;
93 };
94 
measureText(const U16StringPiece & textBuf) const95 float StyleRun::measureText(const U16StringPiece& textBuf) const {
96     TotalAdvancesCompositor compositor;
97     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
98     LayoutCache& layoutCache = LayoutCache::getInstance();
99     for (const BidiText::RunInfo info : BidiText(textBuf, Range(0, textBuf.length()), bidiFlag)) {
100         for (const auto [context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
101             layoutCache.getOrCreate(textBuf.substr(context), piece - context.getStart(), mPaint,
102                                     info.isRtl, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT,
103                                     compositor);
104         }
105     }
106     return compositor.getTotalAdvance();
107 }
108 
109 // Helper class for composing total amount of advance
110 class TotalAdvanceCompositor {
111 public:
TotalAdvanceCompositor(LayoutPieces * outPieces)112     TotalAdvanceCompositor(LayoutPieces* outPieces) : mTotalAdvance(0), mOutPieces(outPieces) {}
113 
setNextContext(const Range & range,HyphenEdit edit,bool dir)114     void setNextContext(const Range& range, HyphenEdit edit, bool dir) {
115         mRange = range;
116         mEdit = edit;
117         mDir = dir;
118     }
119 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint & paint)120     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
121         mTotalAdvance += layoutPiece.advance();
122         if (mOutPieces != nullptr) {
123             mOutPieces->insert(mRange, mEdit, layoutPiece, mDir, paint);
124         }
125     }
126 
advance() const127     float advance() const { return mTotalAdvance; }
128 
129 private:
130     float mTotalAdvance;
131     Range mRange;
132     HyphenEdit mEdit;
133     bool mDir;
134     LayoutPieces* mOutPieces;
135 };
136 
measureHyphenPiece(const U16StringPiece & textBuf,const Range & range,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,LayoutPieces * pieces) const137 float StyleRun::measureHyphenPiece(const U16StringPiece& textBuf, const Range& range,
138                                    StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
139                                    LayoutPieces* pieces) const {
140     TotalAdvanceCompositor compositor(pieces);
141     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
142     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
143         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
144             const StartHyphenEdit startEdit =
145                     piece.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
146             const EndHyphenEdit endEdit =
147                     piece.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
148 
149             compositor.setNextContext(piece, packHyphenEdit(startEdit, endEdit), info.isRtl);
150             LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
151                                                    piece - context.getStart(), mPaint, info.isRtl,
152                                                    startEdit, endEdit, compositor);
153         }
154     }
155     return compositor.advance();
156 }
157 
measure(const U16StringPiece & textBuf,bool computeHyphenation,bool computeLayout,bool ignoreHyphenKerning,MeasuredText * hint)158 void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenation,
159                            bool computeLayout, bool ignoreHyphenKerning, MeasuredText* hint) {
160     if (textBuf.size() == 0) {
161         return;
162     }
163 
164     LayoutPieces* piecesOut = computeLayout ? &layoutPieces : nullptr;
165     CharProcessor proc(textBuf);
166     for (const auto& run : runs) {
167         const Range& range = run->getRange();
168         run->getMetrics(textBuf, &widths, hint ? &hint->layoutPieces : nullptr, piecesOut);
169 
170         if (!computeHyphenation || !run->canBreak()) {
171             continue;
172         }
173 
174         proc.updateLocaleIfNecessary(*run);
175         for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
176             // Even if the run is not a candidate of line break, treat the end of run as the line
177             // break candidate.
178             const bool canBreak = run->canBreak() || (i + 1) == range.getEnd();
179             proc.feedChar(i, textBuf[i], widths[i], canBreak);
180 
181             const uint32_t nextCharOffset = i + 1;
182             if (nextCharOffset != proc.nextWordBreak) {
183                 continue;  // Wait until word break point.
184             }
185 
186             populateHyphenationPoints(textBuf, *run, *proc.hyphenator, proc.contextRange(),
187                                       proc.wordRange(), widths, ignoreHyphenKerning, &hyphenBreaks,
188                                       piecesOut);
189         }
190     }
191 }
192 
193 // Helper class for composing Layout object.
194 class LayoutCompositor {
195 public:
LayoutCompositor(Layout * outLayout,float extraAdvance)196     LayoutCompositor(Layout* outLayout, float extraAdvance)
197             : mOutLayout(outLayout), mExtraAdvance(extraAdvance) {}
198 
setOutOffset(uint32_t outOffset)199     void setOutOffset(uint32_t outOffset) { mOutOffset = outOffset; }
200 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)201     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
202         mOutLayout->appendLayout(layoutPiece, mOutOffset, mExtraAdvance);
203     }
204 
205     uint32_t mOutOffset;
206     Layout* mOutLayout;
207     float mExtraAdvance;
208 };
209 
appendLayout(const U16StringPiece & textBuf,const Range & range,const Range &,const LayoutPieces & pieces,const MinikinPaint & paint,uint32_t outOrigin,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,Layout * outLayout) const210 void StyleRun::appendLayout(const U16StringPiece& textBuf, const Range& range,
211                             const Range& /* context */, const LayoutPieces& pieces,
212                             const MinikinPaint& paint, uint32_t outOrigin,
213                             StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
214                             Layout* outLayout) const {
215     float wordSpacing = range.getLength() == 1 && isWordSpace(textBuf[range.getStart()])
216                                 ? mPaint.wordSpacing
217                                 : 0;
218     bool canUsePrecomputedResult = mPaint == paint;
219 
220     LayoutCompositor compositor(outLayout, wordSpacing);
221     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
222     const uint32_t paintId = pieces.findPaintId(mPaint);
223     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
224         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
225             compositor.setOutOffset(piece.getStart() - outOrigin);
226             const StartHyphenEdit startEdit =
227                     range.getStart() == piece.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
228             const EndHyphenEdit endEdit =
229                     range.getEnd() == piece.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
230 
231             if (canUsePrecomputedResult) {
232                 pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl, startEdit, endEdit,
233                                    paintId, compositor);
234             } else {
235                 LayoutCache::getInstance().getOrCreate(textBuf.substr(context),
236                                                        piece - context.getStart(), paint,
237                                                        info.isRtl, startEdit, endEdit, compositor);
238             }
239         }
240     }
241 }
242 
243 // Helper class for composing bounding box.
244 class BoundsCompositor {
245 public:
BoundsCompositor()246     BoundsCompositor() : mAdvance(0) {}
247 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint & paint)248     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) {
249         MinikinRect pieceBounds;
250         MinikinRect tmpRect;
251         for (uint32_t i = 0; i < layoutPiece.glyphCount(); ++i) {
252             const FakedFont& font = layoutPiece.fontAt(i);
253             const Point& point = layoutPiece.pointAt(i);
254 
255             MinikinFont* minikinFont = font.font->typeface().get();
256             minikinFont->GetBounds(&tmpRect, layoutPiece.glyphIdAt(i), paint, font.fakery);
257             tmpRect.offset(point.x, point.y);
258             pieceBounds.join(tmpRect);
259         }
260         pieceBounds.offset(mAdvance, 0);
261         mBounds.join(pieceBounds);
262         mAdvance += layoutPiece.advance();
263     }
264 
bounds() const265     const MinikinRect& bounds() const { return mBounds; }
advance() const266     float advance() const { return mAdvance; }
267 
268 private:
269     float mAdvance;
270     MinikinRect mBounds;
271 };
272 
getBounds(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const273 std::pair<float, MinikinRect> StyleRun::getBounds(const U16StringPiece& textBuf, const Range& range,
274                                                   const LayoutPieces& pieces) const {
275     BoundsCompositor compositor;
276     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
277     const uint32_t paintId = pieces.findPaintId(mPaint);
278     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
279         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
280             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
281                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
282                                compositor);
283         }
284     }
285     return std::make_pair(compositor.advance(), compositor.bounds());
286 }
287 
288 // Helper class for composing total extent.
289 class ExtentCompositor {
290 public:
ExtentCompositor()291     ExtentCompositor() {}
292 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)293     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
294         mExtent.extendBy(layoutPiece.extent());
295     }
296 
extent() const297     const MinikinExtent& extent() const { return mExtent; }
298 
299 private:
300     MinikinExtent mExtent;
301 };
302 
getExtent(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const303 MinikinExtent StyleRun::getExtent(const U16StringPiece& textBuf, const Range& range,
304                                   const LayoutPieces& pieces) const {
305     ExtentCompositor compositor;
306     Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
307     const uint32_t paintId = pieces.findPaintId(mPaint);
308     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
309         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
310             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
311                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
312                                compositor);
313         }
314     }
315     return compositor.extent();
316 }
317 
buildLayout(const U16StringPiece & textBuf,const Range & range,const Range & contextRange,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)318 Layout MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
319                                  const Range& contextRange, const MinikinPaint& paint,
320                                  StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) {
321     Layout outLayout(range.getLength());
322     for (const auto& run : runs) {
323         const Range& runRange = run->getRange();
324         if (!Range::intersects(range, runRange)) {
325             continue;
326         }
327         const Range targetRange = Range::intersection(runRange, range);
328         StartHyphenEdit startEdit =
329                 targetRange.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
330         EndHyphenEdit endEdit =
331                 targetRange.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
332         run->appendLayout(textBuf, targetRange, contextRange, layoutPieces, paint, range.getStart(),
333                           startEdit, endEdit, &outLayout);
334     }
335     return outLayout;
336 }
337 
getBounds(const U16StringPiece & textBuf,const Range & range) const338 MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) const {
339     MinikinRect rect;
340     float totalAdvance = 0.0f;
341 
342     for (const auto& run : runs) {
343         const Range& runRange = run->getRange();
344         if (!Range::intersects(range, runRange)) {
345             continue;
346         }
347         auto[advance, bounds] =
348                 run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
349         bounds.offset(totalAdvance, 0);
350         rect.join(bounds);
351         totalAdvance += advance;
352     }
353     return rect;
354 }
355 
getExtent(const U16StringPiece & textBuf,const Range & range) const356 MinikinExtent MeasuredText::getExtent(const U16StringPiece& textBuf, const Range& range) const {
357     MinikinExtent extent;
358     for (const auto& run : runs) {
359         const Range& runRange = run->getRange();
360         if (!Range::intersects(range, runRange)) {
361             continue;
362         }
363         MinikinExtent runExtent =
364                 run->getExtent(textBuf, Range::intersection(runRange, range), layoutPieces);
365         extent.extendBy(runExtent);
366     }
367     return extent;
368 }
369 
370 }  // namespace minikin
371