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