1 // Copyright 2019 Google LLC.
2 #include "include/core/SkFontMetrics.h"
3 #include "include/core/SkTextBlob.h"
4 #include "include/private/SkFloatingPoint.h"
5 #include "include/private/SkMalloc.h"
6 #include "include/private/SkTo.h"
7 #include "modules/skparagraph/include/DartTypes.h"
8 #include "modules/skparagraph/include/TextStyle.h"
9 #include "modules/skparagraph/src/ParagraphImpl.h"
10 #include "modules/skparagraph/src/Run.h"
11 #include "modules/skshaper/include/SkShaper.h"
12 #include "src/utils/SkUTF.h"
13
14 #ifdef OHOS_SUPPORT
15 #include "include/FontCollection.h"
16 #include "log.h"
17 #endif
18
19 namespace skia {
20 namespace textlayout {
21 constexpr SkScalar PARAM_TWO = 2.0;
22 #ifdef OHOS_SUPPORT
23 // 1px font size "HarmonyOS Sans" metrics
24 constexpr SkScalar DEFAULT_TOP = -1.056;
25 constexpr SkScalar DEFAULT_BOTTOM = 0.271;
26 constexpr SkScalar DEFAULT_ASCENT = -0.928;
27 constexpr SkScalar DEFAULT_DESCENT = 0.244;
28 struct ScaleParam {
29 SkScalar fontScale;
30 SkScalar baselineShiftScale;
31 };
32 // unordered_map<familyName, ScaleParam>: compress <familyName> font height, shift font baseline.
33 // target font size = font size * ScaleParam.scale.
34 // target baseline = baseline - height * font size * ScaleParam.baselineShiftScale.
35 const std::unordered_map<std::string, ScaleParam> FONT_FAMILY_COMPRESSION_CONFIG = {
36 {"Noto Serif Tibetan", ScaleParam{ .fontScale = 0.79, .baselineShiftScale = 0.1 }},
37 {"Noto Sans Tibetan", ScaleParam{ .fontScale = 0.79, .baselineShiftScale = 0.1 }},
38 };
39 const std::unordered_map<std::string, ScaleParam> FONT_FAMILY_COMPRESSION_WITH_HEIGHT_ADAPTER_CONFIG = {
40 {"Noto Serif Tibetan", ScaleParam{ .fontScale = 0.85, .baselineShiftScale = 0.11 }},
41 {"Noto Sans Tibetan", ScaleParam{ .fontScale = 0.85, .baselineShiftScale = 0.11 }},
42 };
43 const ScaleParam DEFAULT_SCALE_PARAM = ScaleParam{ .fontScale = 0, .baselineShiftScale = 0 };
44 enum FontCompressionStatus {
45 UNDEFINED, // undefined font, the typeface is null.
46 SYSTEM, // system font, need to be compressed.
47 CUSTOM, // custom font, doesn't need to be compressed.
48 };
49 // the font padding does not take effect for these font families.
50 const std::unordered_set<std::string> FONT_PADDING_NOT_EFFECT_FAMILY = {
51 "Harmony Clock_01",
52 "Harmony Clock_02",
53 "Harmony Clock_03",
54 "Harmony Clock_04",
55 "Harmony Clock_05",
56 "Harmony Clock_06",
57 "Harmony Clock_07",
58 "Harmony Clock_08",
59 // symbol: need to ensure "the symbol height = the font size".
60 // so the height compression is not enabled for symbol.
61 "HM Symbol",
62 };
63
64 #ifdef USE_SKIA_TXT
getFontCompressionStatus(const RSFont& font)65 FontCompressionStatus getFontCompressionStatus(const RSFont& font)
66 {
67 auto typeface = font.GetTypeface();
68 if (typeface == nullptr) {
69 return FontCompressionStatus::UNDEFINED;
70 }
71 return typeface->IsCustomTypeface() ? FontCompressionStatus::CUSTOM : FontCompressionStatus::SYSTEM;
72 }
getFamilyNameFromFont(const RSFont& font)73 std::string getFamilyNameFromFont(const RSFont& font)
74 {
75 auto typeface = font.GetTypeface();
76 return typeface == nullptr ? "" : typeface->GetFamilyName();
77 }
78 #else
getFontCompressionStatus(const SkFont& font)79 FontCompressionStatus getFontCompressionStatus(const SkFont& font)
80 {
81 auto typeface = font.refTypeface();
82 if (typeface == nullptr) {
83 return FontCompressionStatus::UNDEFINED;
84 }
85 return typeface->isCustomTypeface() ? FontCompressionStatus::CUSTOM : FontCompressionStatus::SYSTEM;
86 }
getFamilyNameFromFont(const SkFont& font)87 std::string getFamilyNameFromFont(const SkFont& font)
88 {
89 auto typeface = font.refTypeface();
90 if (typeface == nullptr) {
91 return "";
92 }
93 SkString familyName;
94 typeface->getFamilyName(&familyName);
95 return std::string(familyName.c_str(), familyName.size());
96 }
97 #endif
98
99 #ifdef USE_SKIA_TXT
findCompressionConfigWithFont(const RSFont& font)100 const ScaleParam& findCompressionConfigWithFont(const RSFont& font)
101 #else
102 const ScaleParam& findCompressionConfigWithFont(const SkFont& font)
103 #endif
104 {
105 auto fontCompressionStatus = getFontCompressionStatus(font);
106 if (fontCompressionStatus != FontCompressionStatus::SYSTEM) {
107 return DEFAULT_SCALE_PARAM;
108 }
109
110 const auto& config = FontCollection::IsAdapterTextHeightEnabled() ?
111 FONT_FAMILY_COMPRESSION_WITH_HEIGHT_ADAPTER_CONFIG : FONT_FAMILY_COMPRESSION_CONFIG;
112 std::string familyName = getFamilyNameFromFont(font);
113 auto iter = config.find(familyName);
114 if (iter == config.end()) {
115 return DEFAULT_SCALE_PARAM;
116 }
117 return iter->second;
118 }
119
120 #ifdef USE_SKIA_TXT
metricsIncludeFontPadding(RSFontMetrics* metrics, const RSFont& font)121 void metricsIncludeFontPadding(RSFontMetrics* metrics, const RSFont& font)
122 #else
123 void metricsIncludeFontPadding(SkFontMetrics* metrics, const SkFont& font)
124 #endif
125 {
126 if (metrics == nullptr) {
127 return;
128 }
129 auto fontCompressionStatus = getFontCompressionStatus(font);
130 if (fontCompressionStatus == FontCompressionStatus::UNDEFINED) {
131 return;
132 }
133 #ifdef USE_SKIA_TXT
134 SkScalar fontSize = font.GetSize();
135 #else
136 SkScalar fontSize = font.getSize();
137 #endif
138 if (!FontCollection::IsAdapterTextHeightEnabled()) {
139 if (fontCompressionStatus == FontCompressionStatus::SYSTEM &&
140 !SkScalarNearlyZero(findCompressionConfigWithFont(font).fontScale)) {
141 metrics->fAscent = DEFAULT_ASCENT * fontSize;
142 metrics->fDescent = DEFAULT_DESCENT * fontSize;
143 }
144 return;
145 }
146
147 std::string curFamilyName = getFamilyNameFromFont(font);
148 auto setIter = FONT_PADDING_NOT_EFFECT_FAMILY.find(curFamilyName);
149 if (setIter == FONT_PADDING_NOT_EFFECT_FAMILY.end()) {
150 if (fontCompressionStatus == FontCompressionStatus::SYSTEM) {
151 metrics->fAscent = DEFAULT_TOP * fontSize;
152 metrics->fDescent = DEFAULT_BOTTOM * fontSize;
153 return;
154 }
155 // use top and bottom as ascent and descent.
156 // calculate height with top and bottom.(includeFontPadding)
157 metrics->fAscent = metrics->fTop;
158 metrics->fDescent = metrics->fBottom;
159 }
160 }
161
162 #ifdef USE_SKIA_TXT
scaleFontWithCompressionConfig(RSFont& font, ScaleOP op)163 void scaleFontWithCompressionConfig(RSFont& font, ScaleOP op)
164 {
165 SkScalar fontSize = font.GetSize();
166 #else
167 void scaleFontWithCompressionConfig(SkFont& font, ScaleOP op)
168 {
169 SkScalar fontSize = font.getSize();
170 #endif
171 auto config = findCompressionConfigWithFont(font);
172 if (SkScalarNearlyZero(config.fontScale)) {
173 return;
174 }
175 switch (op) {
176 case ScaleOP::COMPRESS:
177 fontSize *= config.fontScale;
178 break;
179 case ScaleOP::DECOMPRESS:
180 fontSize /= config.fontScale;
181 break;
182 default:
183 return;
184 }
185 #ifdef USE_SKIA_TXT
186 font.SetSize(fontSize);
187 #else
188 font.setSize(fontSize);
189 #endif
190 }
191 #endif
192
193 Run::Run(ParagraphImpl* owner,
194 const SkShaper::RunHandler::RunInfo& info,
195 size_t firstChar,
196 SkScalar heightMultiplier,
197 bool useHalfLeading,
198 SkScalar baselineShift,
199 size_t index,
200 SkScalar offsetX)
201 : fOwner(owner)
202 , fTextRange(firstChar + info.utf8Range.begin(), firstChar + info.utf8Range.end())
203 , fClusterRange(EMPTY_CLUSTERS)
204 , fFont(info.fFont)
205 , fClusterStart(firstChar)
206 , fGlyphData(std::make_shared<GlyphData>())
207 , fGlyphs(fGlyphData->glyphs)
208 , fPositions(fGlyphData->positions)
209 , fOffsets(fGlyphData->offsets)
210 , fClusterIndexes(fGlyphData->clusterIndexes)
211 , fHeightMultiplier(heightMultiplier)
212 , fUseHalfLeading(useHalfLeading)
213 , fBaselineShift(baselineShift)
214 {
215 fBidiLevel = info.fBidiLevel;
216 fAdvance = info.fAdvance;
217 fIndex = index;
218 fUtf8Range = info.utf8Range;
219 fOffset = SkVector::Make(offsetX, 0);
220
221 fGlyphs.push_back_n(info.glyphCount);
222 fPositions.push_back_n(info.glyphCount + 1);
223 fOffsets.push_back_n(info.glyphCount + 1);
224 fClusterIndexes.push_back_n(info.glyphCount + 1);
225 fHalfLetterspacings.push_back_n(info.glyphCount + 1);
226 std::fill(fHalfLetterspacings.begin(), fHalfLetterspacings.end(), 0.0);
227 #ifndef USE_SKIA_TXT
228 info.fFont.getMetrics(&fFontMetrics);
229 #else
230 info.fFont.GetMetrics(&fFontMetrics);
231 #endif
232
233 #ifdef OHOS_SUPPORT
234 auto decompressFont = info.fFont;
235 scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
236 metricsIncludeFontPadding(&fFontMetrics, decompressFont);
237 auto config = findCompressionConfigWithFont(decompressFont);
238 fCompressionBaselineShift = (fFontMetrics.fDescent - fFontMetrics.fAscent) * config.baselineShiftScale;
239 #endif
240
241 this->calculateMetrics();
242
243 // To make edge cases easier:
244 fPositions[info.glyphCount] = fOffset + fAdvance;
245 fOffsets[info.glyphCount] = {0, 0};
246 fClusterIndexes[info.glyphCount] = this->leftToRight() ? info.utf8Range.end() : info.utf8Range.begin();
247 fEllipsis = false;
248 fPlaceholderIndex = std::numeric_limits<size_t>::max();
249 }
250
251 void Run::calculateMetrics() {
252 fCorrectAscent = fFontMetrics.fAscent - fFontMetrics.fLeading * 0.5;
253 fCorrectDescent = fFontMetrics.fDescent + fFontMetrics.fLeading * 0.5;
254 fCorrectLeading = 0;
255 if (SkScalarNearlyZero(fHeightMultiplier)) {
256 return;
257 }
258 #ifndef USE_SKIA_TXT
259 const auto runHeight = fHeightMultiplier * fFont.getSize();
260 #else
261 const auto runHeight = fHeightMultiplier * fFont.GetSize();
262 #endif
263 const auto fontIntrinsicHeight = fCorrectDescent - fCorrectAscent;
264 if (fUseHalfLeading) {
265 const auto extraLeading = (runHeight - fontIntrinsicHeight) / 2;
266 fCorrectAscent -= extraLeading;
267 fCorrectDescent += extraLeading;
268 } else {
269 const auto multiplier = runHeight / fontIntrinsicHeight;
270 fCorrectAscent *= multiplier;
271 fCorrectDescent *= multiplier;
272 }
273 // If we shift the baseline we need to make sure the shifted text fits the line
274 fCorrectAscent += fBaselineShift;
275 fCorrectDescent += fBaselineShift;
276 }
277
278 SkShaper::RunHandler::Buffer Run::newRunBuffer() {
279 return {fGlyphs.data(), fPositions.data(), fOffsets.data(), fClusterIndexes.data(), fOffset};
280 }
281
282 #ifndef USE_SKIA_TXT
283 void Run::copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size) const {
284 SkASSERT(pos + size <= this->size());
285 const auto& blobBuffer = builder.allocRunPos(fFont, SkToInt(size));
286 sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
287
288 for (size_t i = 0; i < size; ++i) {
289 auto point = fPositions[i + pos];
290 if (!fJustificationShifts.empty()) {
291 point.fX += fJustificationShifts[i + pos].fX;
292 }
293 if (!fAutoSpacings.empty()) {
294 point.fX += fAutoSpacings[i + pos].fX;
295 }
296 point += fOffsets[i + pos];
297 blobBuffer.points()[i] = point;
298 }
299 }
300 #else
301 void Run::copyTo(RSTextBlobBuilder& builder, size_t pos, size_t size) const {
302 SkASSERT(pos + size <= this->size());
303 const auto& blobBuffer = builder.AllocRunPos(fFont, SkToInt(size));
304 sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
305 auto points = reinterpret_cast<SkPoint*>(blobBuffer.pos);
306
307 for (size_t i = 0; i < size; ++i) {
308 auto point = fPositions[i + pos];
309 if (!fJustificationShifts.empty()) {
310 point.fX += fJustificationShifts[i + pos].fX;
311 }
312 if (!fAutoSpacings.empty()) {
313 point.fX += fAutoSpacings[i + pos].fX;
314 }
315 point += fOffsets[i + pos];
316 points[i] = point;
317 }
318 }
319
320 void Run::copyTo(RSTextBlobBuilder& builder,
321 const RSPath* path,
322 float hOffset,
323 float vOffset,
324 float fTextShift,
325 size_t pos,
326 size_t size) const {
327 SkASSERT(pos + size <= this->size());
328 auto& blobBuffer = builder.AllocRunRSXform(fFont, SkToInt(size));
329 sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
330 std::vector<float> widths(size);
331 fFont.GetWidths(blobBuffer.glyphs, size, widths.data());
332 RSXform* xform = reinterpret_cast<RSXform*>(blobBuffer.pos);
333 for (size_t i = 0; i < size; ++i) {
334 float halfWidth = widths[i + pos] * 0.5f;
335 float x = hOffset + posX(i + pos) + halfWidth + fOffsets[i + pos].x() + fTextShift;
336 if (!fJustificationShifts.empty()) {
337 x += fJustificationShifts[i + pos].fX;
338 }
339 RSPoint rsPos;
340 RSPoint rsTan;
341 if (!path->GetPositionAndTangent(x, rsPos, rsTan, false)) {
342 rsPos.Set(x, vOffset);
343 rsTan.Set(1, 0);
344 }
345 xform[i].cos_ = rsTan.GetX();
346 xform[i].sin_ = rsTan.GetY();
347 xform[i].tx_ = rsPos.GetX() - rsTan.GetY() * vOffset - halfWidth * rsTan.GetX();
348 xform[i].ty_ = rsPos.GetY() + rsTan.GetX() * vOffset - halfWidth * rsTan.GetY();
349 }
350 }
351 #endif
352
353 // Find a cluster range from text range (within one run)
354 // Cluster range is normalized ([start:end) start < end regardless of TextDirection
355 // Boolean value in triple indicates whether the cluster range was found or not
356 std::tuple<bool, ClusterIndex, ClusterIndex> Run::findLimitingClusters(TextRange text) const {
357 if (text.width() == 0) {
358 // Special Flutter case for "\n" and "...\n"
359 if (text.end > this->fTextRange.start) {
360 ClusterIndex index = fOwner->clusterIndex(text.end - 1);
361 return std::make_tuple(true, index, index);
362 } else {
363 return std::make_tuple(false, 0, 0);
364 }
365 }
366
367 ClusterRange clusterRange;
368 bool found = true;
369 // Deal with the case when either start or end are not align with glyph cluster edge
370 // In such case we shift the text range to the right
371 // (cutting from the left and adding to the right)
372 if (leftToRight()) {
373 // LTR: [start:end)
374 found = clusterRange.start != fClusterRange.end;
375 clusterRange.start = fOwner->clusterIndex(text.start);
376 clusterRange.end = fOwner->clusterIndex(text.end - 1);
377 } else {
378 // RTL: (start:end]
379 clusterRange.start = fOwner->clusterIndex(text.end);
380 clusterRange.end = fOwner->clusterIndex(text.start + 1);
381 found = clusterRange.end != fClusterRange.start;
382 }
383
384 return std::make_tuple(
385 found,
386 clusterRange.start,
387 clusterRange.end);
388 }
389
390 std::tuple<bool, TextIndex, TextIndex> Run::findLimitingGlyphClusters(TextRange text) const {
391 TextIndex start = fOwner->findPreviousGlyphClusterBoundary(text.start);
392 TextIndex end = fOwner->findNextGlyphClusterBoundary(text.end);
393 return std::make_tuple(true, start, end);
394 }
395
396 // Adjust the text to grapheme edges so the first grapheme start is in the text and the last grapheme start is in the text
397 // It actually means that the first grapheme is entirely in the text and the last grapheme does not have to be
398 // 12345 234 2:2 -> 2,5 4:4
399 std::tuple<bool, TextIndex, TextIndex> Run::findLimitingGraphemes(TextRange text) const {
400 TextIndex start = fOwner->findPreviousGraphemeBoundary(text.start);
401 TextIndex end = fOwner->findNextGraphemeBoundary(text.end);
402 return std::make_tuple(true, start, end);
403 }
404
405 void Run::iterateThroughClusters(const ClusterVisitor& visitor) {
406
407 for (size_t index = 0; index < fClusterRange.width(); ++index) {
408 auto correctIndex = leftToRight() ? fClusterRange.start + index : fClusterRange.end - index - 1;
409 auto cluster = &fOwner->cluster(correctIndex);
410 visitor(cluster);
411 }
412 }
413
414 void Run::addSpacesAtTheEnd(SkScalar space, Cluster* cluster) {
415 // Increment the run width
416 fAdvance.fX += space;
417 // Increment the cluster width
418 cluster->space(space);
419 }
420
421 SkScalar Run::addSpacesEvenly(SkScalar space) {
422 SkScalar shift = 0;
423 if (this->size()) {
424 shift += space / PARAM_TWO;
425 }
426 for (size_t i = 0; i < this->size(); ++i) {
427 fPositions[i].fX += shift;
428 fHalfLetterspacings[i] = space / PARAM_TWO;
429 shift += space;
430 }
431 if (this->size()) {
432 shift -= space / PARAM_TWO;
433 }
434 fPositions[this->size()].fX += shift;
435 fAdvance.fX += shift;
436 return shift;
437 }
438
439 #ifdef OHOS_SUPPORT
440 SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
441 // Offset all the glyphs in the cluster
442 SkScalar shift = 0;
443 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
444 fPositions[i].fX += shift;
445 fHalfLetterspacings[i] = space / PARAM_TWO;
446 shift += space;
447 }
448 if (this->size() == cluster->endPos()) {
449 // To make calculations easier
450 fPositions[cluster->endPos()].fX += shift;
451 fHalfLetterspacings[cluster->endPos()] = space / PARAM_TWO;
452 }
453 // Increment the run width
454 fAdvance.fX += shift;
455 // Increment the cluster width
456 cluster->space(shift);
457 cluster->setHalfLetterSpacing(space / PARAM_TWO);
458
459 return shift;
460 }
461 #else
462 SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
463 // Offset all the glyphs in the cluster
464 SkScalar shift = 0;
465 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
466 fPositions[i].fX += shift;
467 shift += space;
468 }
469 if (this->size() == cluster->endPos()) {
470 // To make calculations easier
471 fPositions[cluster->endPos()].fX += shift;
472 }
473 // Increment the run width
474 fAdvance.fX += shift;
475 // Increment the cluster width
476 cluster->space(shift);
477 cluster->setHalfLetterSpacing(space / 2);
478
479 return shift;
480 }
481 #endif
482
483 void Run::shift(const Cluster* cluster, SkScalar offset) {
484 if (offset == 0) {
485 return;
486 }
487
488 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
489 fPositions[i].fX += offset;
490 }
491 if (this->size() == cluster->endPos()) {
492 // To make calculations easier
493 fPositions[cluster->endPos()].fX += offset;
494 }
495 }
496
497 #ifdef OHOS_SUPPORT
498 void Run::extendClusterWidth(Cluster* cluster, SkScalar space) {
499 addSpacesAtTheEnd(space, cluster);
500 for (size_t pos = cluster->endPos(); pos < fPositions.size(); pos++) {
501 fPositions[pos].fX += space;
502 }
503 }
504 #endif
505
506 void Run::updateMetrics(InternalLineMetrics* endlineMetrics) {
507
508 SkASSERT(isPlaceholder());
509 auto placeholderStyle = this->placeholderStyle();
510 // Difference between the placeholder baseline and the line bottom
511 SkScalar baselineAdjustment = 0;
512 switch (placeholderStyle->fBaseline) {
513 case TextBaseline::kAlphabetic:
514 break;
515
516 case TextBaseline::kIdeographic:
517 baselineAdjustment = endlineMetrics->deltaBaselines() / 2;
518 break;
519 }
520
521 auto height = placeholderStyle->fHeight;
522 auto offset = placeholderStyle->fBaselineOffset;
523
524 fFontMetrics.fLeading = 0;
525 switch (placeholderStyle->fAlignment) {
526 case PlaceholderAlignment::kBaseline:
527 fFontMetrics.fAscent = baselineAdjustment - height - offset;
528 fFontMetrics.fDescent = baselineAdjustment - offset;
529 break;
530
531 case PlaceholderAlignment::kAboveBaseline:
532 fFontMetrics.fAscent = baselineAdjustment - height;
533 fFontMetrics.fDescent = baselineAdjustment;
534 break;
535
536 case PlaceholderAlignment::kBelowBaseline:
537 fFontMetrics.fAscent = baselineAdjustment;
538 fFontMetrics.fDescent = baselineAdjustment + height;
539 break;
540
541 case PlaceholderAlignment::kTop:
542 fFontMetrics.fAscent = endlineMetrics->ascent();
543 fFontMetrics.fDescent = height + fFontMetrics.fAscent;
544 break;
545
546 case PlaceholderAlignment::kBottom:
547 fFontMetrics.fDescent = endlineMetrics->descent();
548 fFontMetrics.fAscent = fFontMetrics.fDescent - height;
549 break;
550
551 case PlaceholderAlignment::kMiddle:
552 auto mid = (endlineMetrics->ascent() + endlineMetrics->descent()) / PARAM_TWO;
553 fFontMetrics.fDescent = mid + height / PARAM_TWO;
554 fFontMetrics.fAscent = mid - height / PARAM_TWO;
555 break;
556 }
557
558 this->calculateMetrics();
559
560 // Make sure the placeholder can fit the line
561 endlineMetrics->add(this);
562 }
563
564 SkScalar Cluster::sizeToChar(TextIndex ch) const {
565 if (ch < fTextRange.start || ch >= fTextRange.end) {
566 return 0;
567 }
568 auto shift = ch - fTextRange.start;
569 auto ratio = shift * 1.0 / fTextRange.width();
570
571 return SkDoubleToScalar(fWidth * ratio);
572 }
573
574 SkScalar Cluster::sizeFromChar(TextIndex ch) const {
575 if (ch < fTextRange.start || ch >= fTextRange.end) {
576 return 0;
577 }
578 auto shift = fTextRange.end - ch - 1;
579 auto ratio = shift * 1.0 / fTextRange.width();
580
581 return SkDoubleToScalar(fWidth * ratio);
582 }
583
584 size_t Cluster::roundPos(SkScalar s) const {
585 auto ratio = (s * 1.0) / fWidth;
586 return sk_double_floor2int(ratio * size());
587 }
588
589 SkScalar Cluster::trimmedWidth(size_t pos) const {
590 // Find the width until the pos and return the min between trimmedWidth and the width(pos)
591 // We don't have to take in account cluster shift since it's the same for 0 and for pos
592 auto& run = fOwner->run(fRunIndex);
593 SkScalar delta = getHalfLetterSpacing() - run.halfLetterspacing(pos);
594 return std::min(run.positionX(pos) - run.positionX(fStart) + delta, fWidth);
595 }
596
597 SkScalar Run::positionX(size_t pos) const {
598 return posX(pos) + (fJustificationShifts.empty() ? 0 : fJustificationShifts[pos].fY) +
599 (fAutoSpacings.empty() ? 0 : fAutoSpacings[pos].fY);
600 }
601
602 SkScalar Run::posX(size_t index) const {
603 if (index < fPositions.size()) {
604 return fPositions[index].fX;
605 }
606 LOGE("index:%{public}zu,size:%{public}zu", index, fPositions.size());
607 if (fPositions.empty()) {
608 return 0.0f;
609 }
610 return fPositions[fPositions.size() - 1].fX;
611 }
612
613 PlaceholderStyle* Run::placeholderStyle() const {
614 if (isPlaceholder()) {
615 return &fOwner->placeholders()[fPlaceholderIndex].fStyle;
616 } else {
617 return nullptr;
618 }
619 }
620
621 bool Run::isResolved() const {
622 for (auto& glyph :fGlyphs) {
623 if (glyph == 0) {
624 return false;
625 }
626 }
627 return true;
628 }
629
630 Run* Cluster::runOrNull() const {
631 if (fRunIndex >= fOwner->runs().size()) {
632 return nullptr;
633 }
634 return &fOwner->run(fRunIndex);
635 }
636
637 Run& Cluster::run() const {
638 SkASSERT(fRunIndex < fOwner->runs().size());
639 return fOwner->run(fRunIndex);
640 }
641
642 #ifndef USE_SKIA_TXT
643 SkFont Cluster::font() const {
644 #else
645 RSFont Cluster::font() const {
646 #endif
647 SkASSERT(fRunIndex < fOwner->runs().size());
648 return fOwner->run(fRunIndex).font();
649 }
650
651 bool Cluster::isSoftBreak() const {
652 return fOwner->codeUnitHasProperty(fTextRange.end,
653 SkUnicode::CodeUnitFlags::kSoftLineBreakBefore);
654 }
655
656 bool Cluster::isGraphemeBreak() const {
657 return fOwner->codeUnitHasProperty(fTextRange.end, SkUnicode::CodeUnitFlags::kGraphemeStart);
658 }
659 } // namespace textlayout
660 } // namespace skia
661