• 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 #include "minikin/MeasuredText.h"
18 
19 #include "BidiUtils.h"
20 #include "LayoutSplitter.h"
21 #include "LayoutUtils.h"
22 #include "LineBreakerUtil.h"
23 #include "minikin/Characters.h"
24 #include "minikin/Layout.h"
25 
26 namespace minikin {
27 
28 // Helper class for composing character advances.
29 class AdvancesCompositor {
30 public:
AdvancesCompositor(std::vector<float> * outAdvances,std::vector<uint8_t> * flags,LayoutPieces * outPieces)31     AdvancesCompositor(std::vector<float>* outAdvances, std::vector<uint8_t>* flags,
32                        LayoutPieces* outPieces)
33             : mOutAdvances(outAdvances), mFlags(flags), 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,const MinikinRect & bounds)40     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint,
41                     const MinikinRect& bounds) {
42         std::copy(layoutPiece.advances().begin(), layoutPiece.advances().end(),
43                   mOutAdvances->begin() + mRange.getStart());
44 
45         if (bounds.mLeft < 0 || bounds.mRight > layoutPiece.advance()) {
46             for (uint32_t i : mRange) {
47                 (*mFlags)[i] |= MeasuredText::MAY_OVERHANG_BIT;
48             }
49         }
50 
51         if (mOutPieces != nullptr) {
52             mOutPieces->insert(mRange, 0 /* no edit */, layoutPiece, mDir, paint, bounds);
53         }
54     }
55 
56 private:
57     Range mRange;
58     bool mDir;
59     std::vector<float>* mOutAdvances;
60     std::vector<uint8_t>* mFlags;
61     LayoutPieces* mOutPieces;
62 };
63 
getMetrics(const U16StringPiece & textBuf,std::vector<float> * advances,std::vector<uint8_t> * flags,LayoutPieces * precomputed,bool boundsCalculation,LayoutPieces * outPieces) const64 void StyleRun::getMetrics(const U16StringPiece& textBuf, std::vector<float>* advances,
65                           std::vector<uint8_t>* flags, LayoutPieces* precomputed,
66                           bool boundsCalculation, LayoutPieces* outPieces) const {
67     AdvancesCompositor compositor(advances, flags, outPieces);
68     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
69     const uint32_t paintId =
70             (precomputed == nullptr) ? LayoutPieces::kNoPaintId : precomputed->findPaintId(mPaint);
71     for (const BidiText::RunInfo info : BidiText(textBuf, mRange, bidiFlag)) {
72         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
73             compositor.setNextRange(piece, info.isRtl);
74             if (paintId == LayoutPieces::kNoPaintId) {
75                 LayoutCache::getInstance().getOrCreate(
76                         textBuf.substr(context), piece - context.getStart(), mPaint, info.isRtl,
77                         StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, boundsCalculation,
78                         compositor);
79             } else {
80                 precomputed->getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
81                                          StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
82                                          boundsCalculation, compositor);
83             }
84         }
85     }
86 }
87 
88 // Helper class for composing total advances.
89 class TotalAdvancesCompositor {
90 public:
TotalAdvancesCompositor()91     TotalAdvancesCompositor() : mOut(0) {}
92 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &,const MinikinRect &)93     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint&, const MinikinRect&) {
94         for (float w : layoutPiece.advances()) {
95             mOut += w;
96         }
97     }
98 
getTotalAdvance()99     float getTotalAdvance() { return mOut; }
100 
101 private:
102     float mOut;
103 };
104 
measureText(const U16StringPiece & textBuf) const105 float StyleRun::measureText(const U16StringPiece& textBuf) const {
106     TotalAdvancesCompositor compositor;
107     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
108     LayoutCache& layoutCache = LayoutCache::getInstance();
109     for (const BidiText::RunInfo info : BidiText(textBuf, Range(0, textBuf.length()), bidiFlag)) {
110         for (const auto [context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
111             layoutCache.getOrCreate(textBuf.substr(context), piece - context.getStart(), mPaint,
112                                     info.isRtl, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT,
113                                     false /* bounds calculation */, compositor);
114         }
115     }
116     return compositor.getTotalAdvance();
117 }
118 
119 // Helper class for composing total amount of advance
120 class TotalAdvanceCompositor {
121 public:
TotalAdvanceCompositor(LayoutPieces * outPieces)122     TotalAdvanceCompositor(LayoutPieces* outPieces) : mTotalAdvance(0), mOutPieces(outPieces) {}
123 
setNextContext(const Range & range,HyphenEdit edit,bool dir)124     void setNextContext(const Range& range, HyphenEdit edit, bool dir) {
125         mRange = range;
126         mEdit = edit;
127         mDir = dir;
128     }
129 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint & paint,const MinikinRect & bounds)130     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint,
131                     const MinikinRect& bounds) {
132         mTotalAdvance += layoutPiece.advance();
133         if (mOutPieces != nullptr) {
134             mOutPieces->insert(mRange, mEdit, layoutPiece, mDir, paint, bounds);
135         }
136     }
137 
advance() const138     float advance() const { return mTotalAdvance; }
139 
140 private:
141     float mTotalAdvance;
142     Range mRange;
143     HyphenEdit mEdit;
144     bool mDir;
145     LayoutPieces* mOutPieces;
146 };
147 
measureHyphenPiece(const U16StringPiece & textBuf,const Range & range,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,LayoutPieces * pieces) const148 float StyleRun::measureHyphenPiece(const U16StringPiece& textBuf, const Range& range,
149                                    StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
150                                    LayoutPieces* pieces) const {
151     TotalAdvanceCompositor compositor(pieces);
152     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
153     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
154         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
155             const StartHyphenEdit startEdit =
156                     piece.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
157             const EndHyphenEdit endEdit =
158                     piece.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
159 
160             compositor.setNextContext(piece, packHyphenEdit(startEdit, endEdit), info.isRtl);
161             LayoutCache::getInstance().getOrCreate(
162                     textBuf.substr(context), piece - context.getStart(), mPaint, info.isRtl,
163                     startEdit, endEdit, false /* bounds calculation */, compositor);
164         }
165     }
166     return compositor.advance();
167 }
168 
measure(const U16StringPiece & textBuf,bool computeHyphenation,bool computeLayout,bool computeBounds,bool ignoreHyphenKerning,MeasuredText * hint)169 void MeasuredText::measure(const U16StringPiece& textBuf, bool computeHyphenation,
170                            bool computeLayout, bool computeBounds, bool ignoreHyphenKerning,
171                            MeasuredText* hint) {
172     if (textBuf.size() == 0) {
173         return;
174     }
175 
176     LayoutPieces* piecesOut = computeLayout ? &layoutPieces : nullptr;
177     CharProcessor proc(textBuf);
178     for (const auto& run : runs) {
179         const Range& range = run->getRange();
180         run->getMetrics(textBuf, &widths, &flags, hint ? &hint->layoutPieces : nullptr,
181                         computeBounds, piecesOut);
182 
183         if (!computeHyphenation || !run->canBreak()) {
184             continue;
185         }
186 
187         proc.updateLocaleIfNecessary(*run, false /* forceWordStyleAutoToPhrase */);
188         for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
189             // Even if the run is not a candidate of line break, treat the end of run as the line
190             // break candidate.
191             const bool canBreak = run->canBreak() || (i + 1) == range.getEnd();
192             proc.feedChar(i, textBuf[i], widths[i], canBreak);
193 
194             const uint32_t nextCharOffset = i + 1;
195             if (nextCharOffset != proc.nextWordBreak) {
196                 continue;  // Wait until word break point.
197             }
198 
199             populateHyphenationPoints(textBuf, *run, *proc.hyphenator, proc.contextRange(),
200                                       proc.wordRange(), widths, ignoreHyphenKerning, &hyphenBreaks,
201                                       piecesOut);
202         }
203     }
204 }
205 
206 // Helper class for composing Layout object.
207 class LayoutCompositor {
208 public:
LayoutCompositor(Layout * outLayout,float extraAdvance)209     LayoutCompositor(Layout* outLayout, float extraAdvance)
210             : mOutLayout(outLayout), mExtraAdvance(extraAdvance) {}
211 
setOutOffset(uint32_t outOffset)212     void setOutOffset(uint32_t outOffset) { mOutOffset = outOffset; }
213 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &,const MinikinRect &)214     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
215                     const MinikinRect&) {
216         mOutLayout->appendLayout(layoutPiece, mOutOffset, mExtraAdvance);
217     }
218 
219     uint32_t mOutOffset;
220     Layout* mOutLayout;
221     float mExtraAdvance;
222 };
223 
appendLayout(const U16StringPiece & textBuf,const Range & range,const Range &,const LayoutPieces & pieces,const MinikinPaint & paint,uint32_t outOrigin,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,Layout * outLayout) const224 void StyleRun::appendLayout(const U16StringPiece& textBuf, const Range& range,
225                             const Range& /* context */, const LayoutPieces& pieces,
226                             const MinikinPaint& paint, uint32_t outOrigin,
227                             StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
228                             Layout* outLayout) const {
229     bool boundsCalculation = false;
230     float wordSpacing = range.getLength() == 1 && isWordSpace(textBuf[range.getStart()])
231                                 ? mPaint.wordSpacing
232                                 : 0;
233     bool canUsePrecomputedResult = mPaint == paint;
234 
235     LayoutCompositor compositor(outLayout, wordSpacing);
236     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
237     const uint32_t paintId = pieces.findPaintId(mPaint);
238     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
239         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
240             compositor.setOutOffset(piece.getStart() - outOrigin);
241             const StartHyphenEdit startEdit =
242                     range.getStart() == piece.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
243             const EndHyphenEdit endEdit =
244                     range.getEnd() == piece.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
245 
246             if (canUsePrecomputedResult) {
247                 pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl, startEdit, endEdit,
248                                    paintId, boundsCalculation, compositor);
249             } else {
250                 LayoutCache::getInstance().getOrCreate(
251                         textBuf.substr(context), piece - context.getStart(), paint, info.isRtl,
252                         startEdit, endEdit, boundsCalculation, compositor);
253             }
254         }
255     }
256 }
257 
258 // Helper class for composing bounding box.
259 class BoundsCompositor {
260 public:
BoundsCompositor()261     BoundsCompositor() : mAdvance(0) {}
262 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &,const MinikinRect & bounds)263     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
264                     const MinikinRect& bounds) {
265         if (layoutPiece.isVerticalText()) {
266             mBounds.join(bounds, 0, mAdvance);
267         } else {
268             mBounds.join(bounds, mAdvance, 0);
269         }
270         mAdvance += layoutPiece.advance();
271     }
272 
bounds() const273     const MinikinRect& bounds() const { return mBounds; }
advance() const274     float advance() const { return mAdvance; }
275 
276 private:
277     float mAdvance;
278     MinikinRect mBounds;
279 };
280 
getBounds(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const281 std::pair<float, MinikinRect> StyleRun::getBounds(const U16StringPiece& textBuf, const Range& range,
282                                                   const LayoutPieces& pieces) const {
283     BoundsCompositor compositor;
284     const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
285     const uint32_t paintId = pieces.findPaintId(mPaint);
286     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
287         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
288             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
289                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
290                                true /* bounds calculation */, compositor);
291         }
292     }
293     return std::make_pair(compositor.advance(), compositor.bounds());
294 }
295 
296 // Helper class for composing total extent.
297 class ExtentCompositor {
298 public:
ExtentCompositor()299     ExtentCompositor() {}
300 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &,const MinikinRect &)301     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
302                     const MinikinRect&) {
303         mExtent.extendBy(layoutPiece.extent());
304     }
305 
extent() const306     const MinikinExtent& extent() const { return mExtent; }
307 
308 private:
309     MinikinExtent mExtent;
310 };
311 
getExtent(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const312 MinikinExtent StyleRun::getExtent(const U16StringPiece& textBuf, const Range& range,
313                                   const LayoutPieces& pieces) const {
314     ExtentCompositor compositor;
315     Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
316     const uint32_t paintId = pieces.findPaintId(mPaint);
317     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
318         for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
319             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
320                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
321                                false /* bounds calculation */, compositor);
322         }
323     }
324     return compositor.extent();
325 }
326 
327 class LineMetricsCompositor {
328 public:
LineMetricsCompositor()329     LineMetricsCompositor() {}
330 
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &,const MinikinRect & bounds)331     void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */,
332                     const MinikinRect& bounds) {
333         mMetrics.append(layoutPiece.extent(), bounds, layoutPiece.advance());
334     }
335 
metrics() const336     const LineMetrics& metrics() const { return mMetrics; }
337 
338 private:
339     LineMetrics mMetrics;
340 };
341 
getLineMetrics(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const342 LineMetrics StyleRun::getLineMetrics(const U16StringPiece& textBuf, const Range& range,
343                                      const LayoutPieces& pieces) const {
344     LineMetricsCompositor compositor;
345     Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
346     const uint32_t paintId = pieces.findPaintId(mPaint);
347     for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
348         for (const auto [context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
349             pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
350                                StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
351                                true /* bounds calculation */, compositor);
352         }
353     }
354     return compositor.metrics();
355 }
356 
buildLayout(const U16StringPiece & textBuf,const Range & range,const Range & contextRange,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)357 Layout MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
358                                  const Range& contextRange, const MinikinPaint& paint,
359                                  StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) {
360     Layout outLayout(range.getLength());
361     for (const auto& run : runs) {
362         const Range& runRange = run->getRange();
363         if (!Range::intersects(range, runRange)) {
364             continue;
365         }
366         const Range targetRange = Range::intersection(runRange, range);
367         StartHyphenEdit startEdit =
368                 targetRange.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
369         EndHyphenEdit endEdit =
370                 targetRange.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
371         run->appendLayout(textBuf, targetRange, contextRange, layoutPieces, paint, range.getStart(),
372                           startEdit, endEdit, &outLayout);
373     }
374     return outLayout;
375 }
376 
getBounds(const U16StringPiece & textBuf,const Range & range) const377 MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) const {
378     MinikinRect rect;
379     float totalAdvance = 0.0f;
380 
381     for (const auto& run : runs) {
382         const Range& runRange = run->getRange();
383         if (!Range::intersects(range, runRange)) {
384             continue;
385         }
386         auto[advance, bounds] =
387                 run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
388         const MinikinPaint* paint = run->getPaint();
389         if (paint != nullptr) {
390             if (paint->verticalText) {
391                 rect.join(bounds, 0, totalAdvance);
392             } else {
393                 rect.join(bounds, totalAdvance, 0);
394             }
395         } else {
396             rect.join(bounds, totalAdvance, 0);
397         }
398         totalAdvance += advance;
399     }
400     return rect;
401 }
402 
getExtent(const U16StringPiece & textBuf,const Range & range) const403 MinikinExtent MeasuredText::getExtent(const U16StringPiece& textBuf, const Range& range) const {
404     MinikinExtent extent;
405     for (const auto& run : runs) {
406         const Range& runRange = run->getRange();
407         if (!Range::intersects(range, runRange)) {
408             continue;
409         }
410         extent.extendBy(
411                 run->getExtent(textBuf, Range::intersection(runRange, range), layoutPieces));
412     }
413     return extent;
414 }
415 
getLineMetrics(const U16StringPiece & textBuf,const Range & range) const416 LineMetrics MeasuredText::getLineMetrics(const U16StringPiece& textBuf, const Range& range) const {
417     LineMetrics metrics;
418     for (const auto& run : runs) {
419         const Range& runRange = run->getRange();
420         if (!Range::intersects(range, runRange)) {
421             continue;
422         }
423         metrics.append(
424                 run->getLineMetrics(textBuf, Range::intersection(runRange, range), layoutPieces));
425     }
426     return metrics;
427 }
428 
429 }  // namespace minikin
430