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 &)216 void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
217 MinikinRect tmpBounds = layoutPiece.bounds();
218 tmpBounds.offset(mAdvance, 0);
219 mBounds.join(tmpBounds);
220 mAdvance += layoutPiece.advance();
221 }
222
bounds() const223 const MinikinRect& bounds() const { return mBounds; }
advance() const224 float advance() const { return mAdvance; }
225
226 private:
227 float mAdvance;
228 MinikinRect mBounds;
229 };
230
getBounds(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const231 std::pair<float, MinikinRect> StyleRun::getBounds(const U16StringPiece& textBuf, const Range& range,
232 const LayoutPieces& pieces) const {
233 BoundsCompositor compositor;
234 const Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
235 const uint32_t paintId = pieces.findPaintId(mPaint);
236 for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
237 for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
238 pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
239 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
240 compositor);
241 }
242 }
243 return std::make_pair(compositor.advance(), compositor.bounds());
244 }
245
246 // Helper class for composing total extent.
247 class ExtentCompositor {
248 public:
ExtentCompositor()249 ExtentCompositor() {}
250
operator ()(const LayoutPiece & layoutPiece,const MinikinPaint &)251 void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) {
252 mExtent.extendBy(layoutPiece.extent());
253 }
254
extent() const255 const MinikinExtent& extent() const { return mExtent; }
256
257 private:
258 MinikinExtent mExtent;
259 };
260
getExtent(const U16StringPiece & textBuf,const Range & range,const LayoutPieces & pieces) const261 MinikinExtent StyleRun::getExtent(const U16StringPiece& textBuf, const Range& range,
262 const LayoutPieces& pieces) const {
263 ExtentCompositor compositor;
264 Bidi bidiFlag = mIsRtl ? Bidi::FORCE_RTL : Bidi::FORCE_LTR;
265 const uint32_t paintId = pieces.findPaintId(mPaint);
266 for (const BidiText::RunInfo info : BidiText(textBuf, range, bidiFlag)) {
267 for (const auto[context, piece] : LayoutSplitter(textBuf, info.range, info.isRtl)) {
268 pieces.getOrCreate(textBuf, piece, context, mPaint, info.isRtl,
269 StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, paintId,
270 compositor);
271 }
272 }
273 return compositor.extent();
274 }
275
buildLayout(const U16StringPiece & textBuf,const Range & range,const Range & contextRange,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)276 Layout MeasuredText::buildLayout(const U16StringPiece& textBuf, const Range& range,
277 const Range& contextRange, const MinikinPaint& paint,
278 StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) {
279 Layout outLayout(range.getLength());
280 for (const auto& run : runs) {
281 const Range& runRange = run->getRange();
282 if (!Range::intersects(range, runRange)) {
283 continue;
284 }
285 const Range targetRange = Range::intersection(runRange, range);
286 StartHyphenEdit startEdit =
287 targetRange.getStart() == range.getStart() ? startHyphen : StartHyphenEdit::NO_EDIT;
288 EndHyphenEdit endEdit =
289 targetRange.getEnd() == range.getEnd() ? endHyphen : EndHyphenEdit::NO_EDIT;
290 run->appendLayout(textBuf, targetRange, contextRange, layoutPieces, paint, range.getStart(),
291 startEdit, endEdit, &outLayout);
292 }
293 return outLayout;
294 }
295
getBounds(const U16StringPiece & textBuf,const Range & range) const296 MinikinRect MeasuredText::getBounds(const U16StringPiece& textBuf, const Range& range) const {
297 MinikinRect rect;
298 float totalAdvance = 0.0f;
299
300 for (const auto& run : runs) {
301 const Range& runRange = run->getRange();
302 if (!Range::intersects(range, runRange)) {
303 continue;
304 }
305 auto[advance, bounds] =
306 run->getBounds(textBuf, Range::intersection(runRange, range), layoutPieces);
307 bounds.offset(totalAdvance, 0);
308 rect.join(bounds);
309 totalAdvance += advance;
310 }
311 return rect;
312 }
313
getExtent(const U16StringPiece & textBuf,const Range & range) const314 MinikinExtent MeasuredText::getExtent(const U16StringPiece& textBuf, const Range& range) const {
315 MinikinExtent extent;
316 for (const auto& run : runs) {
317 const Range& runRange = run->getRange();
318 if (!Range::intersects(range, runRange)) {
319 continue;
320 }
321 MinikinExtent runExtent =
322 run->getExtent(textBuf, Range::intersection(runRange, range), layoutPieces);
323 extent.extendBy(runExtent);
324 }
325 return extent;
326 }
327
328 } // namespace minikin
329