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