• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     #ifdef OHOS_SUPPORT
305     if (!blobBuffer.glyphs || !fGlyphs.data()) {
306         return;
307     }
308     #endif
309     sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
310     auto points = reinterpret_cast<SkPoint*>(blobBuffer.pos);
311 
312     for (size_t i = 0; i < size; ++i) {
313         auto point = fPositions[i + pos];
314         if (!fJustificationShifts.empty()) {
315             point.fX += fJustificationShifts[i + pos].fX;
316         }
317         if (!fAutoSpacings.empty()) {
318             point.fX += fAutoSpacings[i + pos].fX;
319         }
320         point += fOffsets[i + pos];
321         points[i] = point;
322     }
323 }
324 
325 void Run::copyTo(RSTextBlobBuilder& builder,
326                  const RSPath* path,
327                  float hOffset,
328                  float vOffset,
329                  float fTextShift,
330                  size_t pos,
331                  size_t size) const {
332     SkASSERT(pos + size <= this->size());
333     auto& blobBuffer = builder.AllocRunRSXform(fFont, SkToInt(size));
334     #ifdef OHOS_SUPPORT
335     if (!blobBuffer.glyphs || !fGlyphs.data()) {
336         return;
337     }
338     #endif
339     sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
340     std::vector<float> widths(size);
341     fFont.GetWidths(blobBuffer.glyphs, size, widths.data());
342     RSXform* xform = reinterpret_cast<RSXform*>(blobBuffer.pos);
343     for (size_t i = 0; i < size; ++i) {
344         float halfWidth = widths[i + pos] * 0.5f;
345         float x = hOffset + posX(i + pos) + halfWidth + fOffsets[i + pos].x() + fTextShift;
346         if (!fJustificationShifts.empty()) {
347             x += fJustificationShifts[i + pos].fX;
348         }
349         RSPoint rsPos;
350         RSPoint rsTan;
351         if (!path->GetPositionAndTangent(x, rsPos, rsTan, false)) {
352             rsPos.Set(x, vOffset);
353             rsTan.Set(1, 0);
354         }
355         xform[i].cos_ = rsTan.GetX();
356         xform[i].sin_ = rsTan.GetY();
357         xform[i].tx_ = rsPos.GetX() - rsTan.GetY() * vOffset - halfWidth * rsTan.GetX();
358         xform[i].ty_ = rsPos.GetY() + rsTan.GetX() * vOffset - halfWidth * rsTan.GetY();
359     }
360 }
361 #endif
362 
363 // Find a cluster range from text range (within one run)
364 // Cluster range is normalized ([start:end) start < end regardless of TextDirection
365 // Boolean value in triple indicates whether the cluster range was found or not
366 std::tuple<bool, ClusterIndex, ClusterIndex> Run::findLimitingClusters(TextRange text) const {
367     if (text.width() == 0) {
368         // Special Flutter case for "\n" and "...\n"
369         if (text.end > this->fTextRange.start) {
370             ClusterIndex index = fOwner->clusterIndex(text.end - 1);
371             return std::make_tuple(true, index, index);
372         } else {
373             return std::make_tuple(false, 0, 0);
374         }
375     }
376 
377     ClusterRange clusterRange;
378     bool found = true;
379     // Deal with the case when either start or end are not align with glyph cluster edge
380     // In such case we shift the text range to the right
381     // (cutting from the left and adding to the right)
382     if (leftToRight()) {
383         // LTR: [start:end)
384         found = clusterRange.start != fClusterRange.end;
385         clusterRange.start = fOwner->clusterIndex(text.start);
386         clusterRange.end = fOwner->clusterIndex(text.end - 1);
387     } else {
388         // RTL: (start:end]
389         clusterRange.start = fOwner->clusterIndex(text.end);
390         clusterRange.end = fOwner->clusterIndex(text.start + 1);
391         found = clusterRange.end != fClusterRange.start;
392     }
393 
394     return std::make_tuple(
395             found,
396             clusterRange.start,
397             clusterRange.end);
398 }
399 
400 std::tuple<bool, TextIndex, TextIndex> Run::findLimitingGlyphClusters(TextRange text) const {
401     TextIndex start = fOwner->findPreviousGlyphClusterBoundary(text.start);
402     TextIndex end = fOwner->findNextGlyphClusterBoundary(text.end);
403     return std::make_tuple(true, start, end);
404 }
405 
406 // Adjust the text to grapheme edges so the first grapheme start is in the text and the last grapheme start is in the text
407 // It actually means that the first grapheme is entirely in the text and the last grapheme does not have to be
408 // 12345 234 2:2 -> 2,5 4:4
409 std::tuple<bool, TextIndex, TextIndex> Run::findLimitingGraphemes(TextRange text) const {
410     TextIndex start = fOwner->findPreviousGraphemeBoundary(text.start);
411     TextIndex end = fOwner->findNextGraphemeBoundary(text.end);
412     return std::make_tuple(true, start, end);
413 }
414 
415 void Run::iterateThroughClusters(const ClusterVisitor& visitor) {
416 
417     for (size_t index = 0; index < fClusterRange.width(); ++index) {
418         auto correctIndex = leftToRight() ? fClusterRange.start + index : fClusterRange.end - index - 1;
419         auto cluster = &fOwner->cluster(correctIndex);
420         visitor(cluster);
421     }
422 }
423 
424 void Run::addSpacesAtTheEnd(SkScalar space, Cluster* cluster) {
425     // Increment the run width
426     fAdvance.fX += space;
427     // Increment the cluster width
428     cluster->space(space);
429 }
430 
431 SkScalar Run::addSpacesEvenly(SkScalar space) {
432     SkScalar shift = 0;
433     if (this->size()) {
434         shift += space / PARAM_TWO;
435     }
436     for (size_t i = 0; i < this->size(); ++i) {
437         fPositions[i].fX += shift;
438         fHalfLetterspacings[i] = space / PARAM_TWO;
439         shift += space;
440     }
441     if (this->size()) {
442         shift -= space / PARAM_TWO;
443     }
444     fPositions[this->size()].fX += shift;
445     fAdvance.fX += shift;
446     return shift;
447 }
448 
449 #ifdef OHOS_SUPPORT
450 SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
451     // Offset all the glyphs in the cluster
452     SkScalar shift = 0;
453     for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
454         fPositions[i].fX += shift;
455         fHalfLetterspacings[i] = space / PARAM_TWO;
456         shift += space;
457     }
458     if (this->size() == cluster->endPos()) {
459         // To make calculations easier
460         fPositions[cluster->endPos()].fX += shift;
461         fHalfLetterspacings[cluster->endPos()] = space / PARAM_TWO;
462     }
463     // Increment the run width
464     fAdvance.fX += shift;
465     // Increment the cluster width
466     cluster->space(shift);
467     cluster->setHalfLetterSpacing(space / PARAM_TWO);
468 
469     return shift;
470 }
471 #else
472 SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
473     // Offset all the glyphs in the cluster
474     SkScalar shift = 0;
475     for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
476         fPositions[i].fX += shift;
477         shift += space;
478     }
479     if (this->size() == cluster->endPos()) {
480         // To make calculations easier
481         fPositions[cluster->endPos()].fX += shift;
482     }
483     // Increment the run width
484     fAdvance.fX += shift;
485     // Increment the cluster width
486     cluster->space(shift);
487     cluster->setHalfLetterSpacing(space / 2);
488 
489     return shift;
490 }
491 #endif
492 
493 void Run::shift(const Cluster* cluster, SkScalar offset) {
494     if (offset == 0) {
495         return;
496     }
497 
498     for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
499         fPositions[i].fX += offset;
500     }
501     if (this->size() == cluster->endPos()) {
502         // To make calculations easier
503         fPositions[cluster->endPos()].fX += offset;
504     }
505 }
506 
507 void Run::updateMetrics(InternalLineMetrics* endlineMetrics) {
508 
509     SkASSERT(isPlaceholder());
510     auto placeholderStyle = this->placeholderStyle();
511     // Difference between the placeholder baseline and the line bottom
512     SkScalar baselineAdjustment = 0;
513     switch (placeholderStyle->fBaseline) {
514         case TextBaseline::kAlphabetic:
515             break;
516 
517         case TextBaseline::kIdeographic:
518             baselineAdjustment = endlineMetrics->deltaBaselines() / 2;
519             break;
520     }
521 
522     auto height = placeholderStyle->fHeight;
523     auto offset = placeholderStyle->fBaselineOffset;
524 
525     fFontMetrics.fLeading = 0;
526     switch (placeholderStyle->fAlignment) {
527         case PlaceholderAlignment::kBaseline:
528             fFontMetrics.fAscent = baselineAdjustment - height - offset;
529             fFontMetrics.fDescent = baselineAdjustment - offset;
530             break;
531 
532         case PlaceholderAlignment::kAboveBaseline:
533             fFontMetrics.fAscent = baselineAdjustment - height;
534             fFontMetrics.fDescent = baselineAdjustment;
535             break;
536 
537         case PlaceholderAlignment::kBelowBaseline:
538             fFontMetrics.fAscent = baselineAdjustment;
539             fFontMetrics.fDescent = baselineAdjustment + height;
540             break;
541 
542         case PlaceholderAlignment::kTop:
543             fFontMetrics.fAscent = endlineMetrics->ascent();
544             fFontMetrics.fDescent = height + fFontMetrics.fAscent;
545             break;
546 
547         case PlaceholderAlignment::kBottom:
548             fFontMetrics.fDescent = endlineMetrics->descent();
549             fFontMetrics.fAscent = fFontMetrics.fDescent - height;
550             break;
551 
552         case PlaceholderAlignment::kMiddle:
553             auto mid = (endlineMetrics->ascent() + endlineMetrics->descent()) / PARAM_TWO;
554             fFontMetrics.fDescent = mid + height / PARAM_TWO;
555             fFontMetrics.fAscent = mid - height / PARAM_TWO;
556             break;
557     }
558 
559     this->calculateMetrics();
560 
561     // Make sure the placeholder can fit the line
562     endlineMetrics->add(this);
563 }
564 
565 SkScalar Cluster::sizeToChar(TextIndex ch) const {
566     if (ch < fTextRange.start || ch >= fTextRange.end) {
567         return 0;
568     }
569     auto shift = ch - fTextRange.start;
570     auto ratio = shift * 1.0 / fTextRange.width();
571 
572     return SkDoubleToScalar(fWidth * ratio);
573 }
574 
575 SkScalar Cluster::sizeFromChar(TextIndex ch) const {
576     if (ch < fTextRange.start || ch >= fTextRange.end) {
577         return 0;
578     }
579     auto shift = fTextRange.end - ch - 1;
580     auto ratio = shift * 1.0 / fTextRange.width();
581 
582     return SkDoubleToScalar(fWidth * ratio);
583 }
584 
585 size_t Cluster::roundPos(SkScalar s) const {
586     auto ratio = (s * 1.0) / fWidth;
587     return sk_double_floor2int(ratio * size());
588 }
589 
590 SkScalar Cluster::trimmedWidth(size_t pos) const {
591     // Find the width until the pos and return the min between trimmedWidth and the width(pos)
592     // We don't have to take in account cluster shift since it's the same for 0 and for pos
593     auto& run = fOwner->run(fRunIndex);
594     SkScalar delta = getHalfLetterSpacing() - run.halfLetterspacing(pos);
595     return std::min(run.positionX(pos) - run.positionX(fStart) + delta, fWidth);
596 }
597 
598 SkScalar Run::positionX(size_t pos) const {
599     return posX(pos) + (fJustificationShifts.empty() ? 0 : fJustificationShifts[pos].fY) +
600         (fAutoSpacings.empty() ? 0 : fAutoSpacings[pos].fY);
601 }
602 
603 SkScalar Run::posX(size_t index) const {
604     if (index < fPositions.size()) {
605         return fPositions[index].fX;
606     }
607     LOGE("index:%{public}zu,size:%{public}zu", index, fPositions.size());
608     if (fPositions.empty()) {
609         return 0.0f;
610     }
611     return fPositions[fPositions.size() - 1].fX;
612 }
613 
614 PlaceholderStyle* Run::placeholderStyle() const {
615     if (isPlaceholder()) {
616         return &fOwner->placeholders()[fPlaceholderIndex].fStyle;
617     } else {
618         return nullptr;
619     }
620 }
621 
622 bool Run::isResolved() const {
623     for (auto& glyph :fGlyphs) {
624         if (glyph == 0) {
625             return false;
626         }
627     }
628     return true;
629 }
630 
631 Run* Cluster::runOrNull() const {
632     if (fRunIndex >= fOwner->runs().size()) {
633         return nullptr;
634     }
635     return &fOwner->run(fRunIndex);
636 }
637 
638 Run& Cluster::run() const {
639     SkASSERT(fRunIndex < fOwner->runs().size());
640     return fOwner->run(fRunIndex);
641 }
642 
643 #ifndef USE_SKIA_TXT
644 SkFont Cluster::font() const {
645 #else
646 RSFont Cluster::font() const {
647 #endif
648     SkASSERT(fRunIndex < fOwner->runs().size());
649     return fOwner->run(fRunIndex).font();
650 }
651 
652 bool Cluster::isSoftBreak() const {
653     return fOwner->codeUnitHasProperty(fTextRange.end,
654                                        SkUnicode::CodeUnitFlags::kSoftLineBreakBefore);
655 }
656 
657 bool Cluster::isGraphemeBreak() const {
658     return fOwner->codeUnitHasProperty(fTextRange.end, SkUnicode::CodeUnitFlags::kGraphemeStart);
659 }
660 }  // namespace textlayout
661 }  // namespace skia
662