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 void Run::updateMetrics(InternalLineMetrics* endlineMetrics) {
514
515 SkASSERT(isPlaceholder());
516 auto placeholderStyle = this->placeholderStyle();
517 // Difference between the placeholder baseline and the line bottom
518 SkScalar baselineAdjustment = 0;
519 switch (placeholderStyle->fBaseline) {
520 case TextBaseline::kAlphabetic:
521 break;
522
523 case TextBaseline::kIdeographic:
524 baselineAdjustment = endlineMetrics->deltaBaselines() / 2;
525 break;
526 }
527
528 auto height = placeholderStyle->fHeight;
529 auto offset = placeholderStyle->fBaselineOffset;
530
531 fFontMetrics.fLeading = 0;
532 switch (placeholderStyle->fAlignment) {
533 case PlaceholderAlignment::kBaseline:
534 fFontMetrics.fAscent = baselineAdjustment - height - offset;
535 fFontMetrics.fDescent = baselineAdjustment - offset;
536 break;
537
538 case PlaceholderAlignment::kAboveBaseline:
539 fFontMetrics.fAscent = baselineAdjustment - height;
540 fFontMetrics.fDescent = baselineAdjustment;
541 break;
542
543 case PlaceholderAlignment::kBelowBaseline:
544 fFontMetrics.fAscent = baselineAdjustment;
545 fFontMetrics.fDescent = baselineAdjustment + height;
546 break;
547
548 case PlaceholderAlignment::kTop:
549 fFontMetrics.fAscent = endlineMetrics->ascent();
550 fFontMetrics.fDescent = height + fFontMetrics.fAscent;
551 break;
552
553 case PlaceholderAlignment::kBottom:
554 fFontMetrics.fDescent = endlineMetrics->descent();
555 fFontMetrics.fAscent = fFontMetrics.fDescent - height;
556 break;
557
558 case PlaceholderAlignment::kMiddle:
559 auto mid = (endlineMetrics->ascent() + endlineMetrics->descent()) / PARAM_TWO;
560 fFontMetrics.fDescent = mid + height / PARAM_TWO;
561 fFontMetrics.fAscent = mid - height / PARAM_TWO;
562 break;
563 }
564
565 this->calculateMetrics();
566
567 // Make sure the placeholder can fit the line
568 endlineMetrics->add(this);
569 }
570
571 SkScalar Cluster::sizeToChar(TextIndex ch) const {
572 if (ch < fTextRange.start || ch >= fTextRange.end) {
573 return 0;
574 }
575 auto shift = ch - fTextRange.start;
576 auto ratio = shift * 1.0 / fTextRange.width();
577
578 return SkDoubleToScalar(fWidth * ratio);
579 }
580
581 SkScalar Cluster::sizeFromChar(TextIndex ch) const {
582 if (ch < fTextRange.start || ch >= fTextRange.end) {
583 return 0;
584 }
585 auto shift = fTextRange.end - ch - 1;
586 auto ratio = shift * 1.0 / fTextRange.width();
587
588 return SkDoubleToScalar(fWidth * ratio);
589 }
590
591 size_t Cluster::roundPos(SkScalar s) const {
592 auto ratio = (s * 1.0) / fWidth;
593 return sk_double_floor2int(ratio * size());
594 }
595
596 SkScalar Cluster::trimmedWidth(size_t pos) const {
597 // Find the width until the pos and return the min between trimmedWidth and the width(pos)
598 // We don't have to take in account cluster shift since it's the same for 0 and for pos
599 auto& run = fOwner->run(fRunIndex);
600 SkScalar delta = getHalfLetterSpacing() - run.halfLetterspacing(pos);
601 return std::min(run.positionX(pos) - run.positionX(fStart) + delta, fWidth);
602 }
603
604 SkScalar Run::positionX(size_t pos) const {
605 return posX(pos) + (fJustificationShifts.empty() ? 0 : fJustificationShifts[pos].fY) +
606 (fAutoSpacings.empty() ? 0 : fAutoSpacings[pos].fY);
607 }
608
609 SkScalar Run::posX(size_t index) const {
610 if (index < fPositions.size()) {
611 return fPositions[index].fX;
612 }
613 LOGE("index:%{public}zu,size:%{public}zu", index, fPositions.size());
614 if (fPositions.empty()) {
615 return 0.0f;
616 }
617 return fPositions[fPositions.size() - 1].fX;
618 }
619
620 PlaceholderStyle* Run::placeholderStyle() const {
621 if (isPlaceholder()) {
622 return &fOwner->placeholders()[fPlaceholderIndex].fStyle;
623 } else {
624 return nullptr;
625 }
626 }
627
628 bool Run::isResolved() const {
629 for (auto& glyph :fGlyphs) {
630 if (glyph == 0) {
631 return false;
632 }
633 }
634 return true;
635 }
636
637 Run* Cluster::runOrNull() const {
638 if (fRunIndex >= fOwner->runs().size()) {
639 return nullptr;
640 }
641 return &fOwner->run(fRunIndex);
642 }
643
644 Run& Cluster::run() const {
645 SkASSERT(fRunIndex < fOwner->runs().size());
646 return fOwner->run(fRunIndex);
647 }
648
649 #ifndef USE_SKIA_TXT
650 SkFont Cluster::font() const {
651 #else
652 RSFont Cluster::font() const {
653 #endif
654 SkASSERT(fRunIndex < fOwner->runs().size());
655 return fOwner->run(fRunIndex).font();
656 }
657
658 bool Cluster::isSoftBreak() const {
659 return fOwner->codeUnitHasProperty(fTextRange.end,
660 SkUnicode::CodeUnitFlags::kSoftLineBreakBefore);
661 }
662
663 bool Cluster::isGraphemeBreak() const {
664 return fOwner->codeUnitHasProperty(fTextRange.end, SkUnicode::CodeUnitFlags::kGraphemeStart);
665 }
666 } // namespace textlayout
667 } // namespace skia
668