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