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
14namespace skia {
15namespace textlayout {
16
17Run::Run(ParagraphImpl* owner,
18 const SkShaper::RunHandler::RunInfo& info,
19 size_t firstChar,
20 SkScalar heightMultiplier,
21 size_t index,
22 SkScalar offsetX)
23 : fOwner(owner)
24 , fTextRange(firstChar + info.utf8Range.begin(), firstChar + info.utf8Range.end())
25 , fClusterRange(EMPTY_CLUSTERS)
26 , fFont(info.fFont)
27 , fClusterStart(firstChar)
28 , fHeightMultiplier(heightMultiplier)
29{
30 fBidiLevel = info.fBidiLevel;
31 fAdvance = info.fAdvance;
32 fIndex = index;
33 fUtf8Range = info.utf8Range;
34 fOffset = SkVector::Make(offsetX, 0);
35 fGlyphs.push_back_n(info.glyphCount);
36 fBounds.push_back_n(info.glyphCount);
37 fPositions.push_back_n(info.glyphCount + 1);
38 fClusterIndexes.push_back_n(info.glyphCount + 1);
39 fShifts.push_back_n(info.glyphCount + 1, 0.0);
40 info.fFont.getMetrics(&fFontMetrics);
41
42 this->calculateMetrics();
43
44 fSpaced = false;
45 // To make edge cases easier:
46 fPositions[info.glyphCount] = fOffset + fAdvance;
47 fClusterIndexes[info.glyphCount] = this->leftToRight() ? info.utf8Range.end() : info.utf8Range.begin();
48 fEllipsis = false;
49 fPlaceholderIndex = std::numeric_limits<size_t>::max();
50}
51
52void Run::calculateMetrics() {
53 fCorrectAscent = fFontMetrics.fAscent - fFontMetrics.fLeading * 0.5;
54 fCorrectDescent = fFontMetrics.fDescent + fFontMetrics.fLeading * 0.5;
55 fCorrectLeading = 0;
56 if (!SkScalarNearlyZero(fHeightMultiplier)) {
57 auto multiplier = fHeightMultiplier * fFont.getSize() /
58 (fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
59 fCorrectAscent *= multiplier;
60 fCorrectDescent *= multiplier;
61 }
62}
63
64SkShaper::RunHandler::Buffer Run::newRunBuffer() {
65 return {fGlyphs.data(), fPositions.data(), nullptr, fClusterIndexes.data(), fOffset};
66}
67
68void Run::commit() {
69 fFont.getBounds(fGlyphs.data(), fGlyphs.size(), fBounds.data(), nullptr);
70}
71SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
72 SkASSERT(start <= end);
73 // clip |= end == size(); // Clip at the end of the run?
74 SkScalar shift = 0;
75 if (fSpaced && end > start) {
76 shift = fShifts[clip ? end - 1 : end] - fShifts[start];
77 }
78 auto correction = 0.0f;
79 if (end > start && !fJustificationShifts.empty()) {
80 // This is not a typo: we are using Point as a pair of SkScalars
81 correction = fJustificationShifts[end - 1].fX -
82 fJustificationShifts[start].fY;
83 }
84 return posX(end) - posX(start) + shift + correction;
85}
86
87void Run::copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size) const {
88 SkASSERT(pos + size <= this->size());
89 const auto& blobBuffer = builder.allocRunPos(fFont, SkToInt(size));
90 sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
91
92 if (!fSpaced && fJustificationShifts.empty()) {
93 sk_careful_memcpy(blobBuffer.points(), fPositions.data() + pos, size * sizeof(SkPoint));
94 } else {
95 for (size_t i = 0; i < size; ++i) {
96 auto point = fPositions[i + pos];
97 if (fSpaced) {
98 point.fX += fShifts[i + pos];
99 }
100 if (!fJustificationShifts.empty()) {
101 point.fX += fJustificationShifts[i + pos].fX;
102 }
103 blobBuffer.points()[i] = point;
104 }
105 }
106}
107
108// Find a cluster range from text range (within one run)
109// Cluster range is normalized ([start:end) start < end regardless of TextDirection
110// Boolean value in triple indicates whether the cluster range was found or not
111std::tuple<bool, ClusterIndex, ClusterIndex> Run::findLimitingClusters(TextRange text) const {
112 if (text.width() == 0) {
113 // Special Flutter case for "\n" and "...\n"
114 if (text.end > this->fTextRange.start) {
115 ClusterIndex index = fOwner->clusterIndex(text.end - 1);
116 return std::make_tuple(true, index, index);
117 } else {
118 return std::make_tuple(false, 0, 0);
119 }
120 }
121
122 ClusterIndex startIndex = fOwner->clusterIndex(text.start);
123 ClusterIndex endIndex = fOwner->clusterIndex(text.end - 1);
124 if (!leftToRight()) {
125 std::swap(startIndex, endIndex);
126 }
127 return std::make_tuple(startIndex != fClusterRange.end && endIndex != fClusterRange.end, startIndex, endIndex);
128}
129
130void Run::iterateThroughClustersInTextOrder(const ClusterTextVisitor& visitor) {
131 // Can't figure out how to do it with one code for both cases without 100 ifs
132 // Can't go through clusters because there are no cluster table yet
133 if (leftToRight()) {
134 size_t start = 0;
135 size_t cluster = this->clusterIndex(start);
136 for (size_t glyph = 1; glyph <= this->size(); ++glyph) {
137 auto nextCluster = this->clusterIndex(glyph);
138 if (nextCluster <= cluster) {
139 continue;
140 }
141
142 visitor(start,
143 glyph,
144 fClusterStart + cluster,
145 fClusterStart + nextCluster,
146 this->calculateWidth(start, glyph, glyph == size()),
147 this->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS));
148
149 start = glyph;
150 cluster = nextCluster;
151 }
152 } else {
153 size_t glyph = this->size();
154 size_t cluster = this->fUtf8Range.begin();
155 for (int32_t start = this->size() - 1; start >= 0; --start) {
156 size_t nextCluster =
157 start == 0 ? this->fUtf8Range.end() : this->clusterIndex(start - 1);
158 if (nextCluster <= cluster) {
159 continue;
160 }
161
162 visitor(start,
163 glyph,
164 fClusterStart + cluster,
165 fClusterStart + nextCluster,
166 this->calculateWidth(start, glyph, glyph == 0),
167 this->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS));
168
169 glyph = start;
170 cluster = nextCluster;
171 }
172 }
173}
174
175void Run::iterateThroughClusters(const ClusterVisitor& visitor) {
176
177 for (size_t index = 0; index < fClusterRange.width(); ++index) {
178 auto correctIndex = leftToRight() ? fClusterRange.start + index : fClusterRange.end - index - 1;
179 auto cluster = &fOwner->cluster(correctIndex);
180 visitor(cluster);
181 }
182}
183
184SkScalar Run::addSpacesAtTheEnd(SkScalar space, Cluster* cluster) {
185 if (cluster->endPos() == cluster->startPos()) {
186 return 0;
187 }
188
189 fShifts[cluster->endPos() - 1] += space;
190 // Increment the run width
191 fSpaced = true;
192 fAdvance.fX += space;
193 // Increment the cluster width
194 cluster->space(space, space);
195
196 return space;
197}
198
199SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
200 // Offset all the glyphs in the cluster
201 SkScalar shift = 0;
202 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
203 fShifts[i] += shift;
204 shift += space;
205 }
206 if (this->size() == cluster->endPos()) {
207 // To make calculations easier
208 fShifts[cluster->endPos()] += shift;
209 }
210 // Increment the run width
211 fSpaced = true;
212 fAdvance.fX += shift;
213 // Increment the cluster width
214 cluster->space(shift, space);
215 cluster->setHalfLetterSpacing(space / 2);
216
217 return shift;
218}
219
220void Run::shift(const Cluster* cluster, SkScalar offset) {
221 if (offset == 0) {
222 return;
223 }
224
225 fSpaced = true;
226 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
227 fShifts[i] += offset;
228 }
229 if (this->size() == cluster->endPos()) {
230 // To make calculations easier
231 fShifts[cluster->endPos()] += offset;
232 }
233}
234
235void Run::updateMetrics(InternalLineMetrics* endlineMetrics) {
236
237 SkASSERT(isPlaceholder());
238 auto placeholderStyle = this->placeholderStyle();
239 // Difference between the placeholder baseline and the line bottom
240 SkScalar baselineAdjustment = 0;
241 switch (placeholderStyle->fBaseline) {
242 case TextBaseline::kAlphabetic:
243 break;
244
245 case TextBaseline::kIdeographic:
246 baselineAdjustment = endlineMetrics->deltaBaselines() / 2;
247 break;
248 }
249
250 auto height = placeholderStyle->fHeight;
251 auto offset = placeholderStyle->fBaselineOffset;
252
253 fFontMetrics.fLeading = 0;
254 switch (placeholderStyle->fAlignment) {
255 case PlaceholderAlignment::kBaseline:
256 fFontMetrics.fAscent = baselineAdjustment - offset;
257 fFontMetrics.fDescent = baselineAdjustment + height - offset;
258 break;
259
260 case PlaceholderAlignment::kAboveBaseline:
261 fFontMetrics.fAscent = baselineAdjustment - height;
262 fFontMetrics.fDescent = baselineAdjustment;
263 break;
264
265 case PlaceholderAlignment::kBelowBaseline:
266 fFontMetrics.fAscent = baselineAdjustment;
267 fFontMetrics.fDescent = baselineAdjustment + height;
268 break;
269
270 case PlaceholderAlignment::kTop:
271 fFontMetrics.fDescent = height + fFontMetrics.fAscent;
272 break;
273
274 case PlaceholderAlignment::kBottom:
275 fFontMetrics.fAscent = fFontMetrics.fDescent - height;
276 break;
277
278 case PlaceholderAlignment::kMiddle:
279 auto mid = (-fFontMetrics.fDescent - fFontMetrics.fAscent)/2.0;
280 fFontMetrics.fDescent = height/2.0 - mid;
281 fFontMetrics.fAscent = - height/2.0 - mid;
282 break;
283 }
284
285 this->calculateMetrics();
286
287 // Make sure the placeholder can fit the line
288 endlineMetrics->add(this);
289}
290
291SkScalar Cluster::sizeToChar(TextIndex ch) const {
292 if (ch < fTextRange.start || ch >= fTextRange.end) {
293 return 0;
294 }
295 auto shift = ch - fTextRange.start;
296 auto ratio = shift * 1.0 / fTextRange.width();
297
298 return SkDoubleToScalar(fWidth * ratio);
299}
300
301SkScalar Cluster::sizeFromChar(TextIndex ch) const {
302 if (ch < fTextRange.start || ch >= fTextRange.end) {
303 return 0;
304 }
305 auto shift = fTextRange.end - ch - 1;
306 auto ratio = shift * 1.0 / fTextRange.width();
307
308 return SkDoubleToScalar(fWidth * ratio);
309}
310
311size_t Cluster::roundPos(SkScalar s) const {
312 auto ratio = (s * 1.0) / fWidth;
313 return sk_double_floor2int(ratio * size());
314}
315
316SkScalar Cluster::trimmedWidth(size_t pos) const {
317 // Find the width until the pos and return the min between trimmedWidth and the width(pos)
318 // We don't have to take in account cluster shift since it's the same for 0 and for pos
319 auto& run = fOwner->run(fRunIndex);
320 return std::min(run.positionX(pos) - run.positionX(fStart), fWidth);
321}
322
323SkScalar Run::positionX(size_t pos) const {
324 return posX(pos) + fShifts[pos] +
325 (fJustificationShifts.empty() ? 0 : fJustificationShifts[pos].fY);
326}
327
328PlaceholderStyle* Run::placeholderStyle() const {
329 if (isPlaceholder()) {
330 return &fOwner->placeholders()[fPlaceholderIndex].fStyle;
331 } else {
332 return nullptr;
333 }
334}
335
336Run* Cluster::run() const {
337 if (fRunIndex >= fOwner->runs().size()) {
338 return nullptr;
339 }
340 return &fOwner->run(fRunIndex);
341}
342
343SkFont Cluster::font() const {
344 return fOwner->run(fRunIndex).font();
345}
346
347bool Cluster::isHardBreak() const {
348 return fOwner->codeUnitHasProperty(fTextRange.end, CodeUnitFlags::kHardLineBreakBefore);
349}
350
351bool Cluster::isSoftBreak() const {
352 return fOwner->codeUnitHasProperty(fTextRange.end, CodeUnitFlags::kSoftLineBreakBefore);
353}
354
355bool Cluster::isGraphemeBreak() const {
356 return fOwner->codeUnitHasProperty(fTextRange.end, CodeUnitFlags::kGraphemeStart);
357}
358
359Cluster::Cluster(ParagraphImpl* owner,
360 RunIndex runIndex,
361 size_t start,
362 size_t end,
363 SkSpan<const char> text,
364 SkScalar width,
365 SkScalar height)
366 : fOwner(owner)
367 , fRunIndex(runIndex)
368 , fTextRange(text.begin() - fOwner->text().begin(), text.end() - fOwner->text().begin())
369 , fGraphemeRange(EMPTY_RANGE)
370 , fStart(start)
371 , fEnd(end)
372 , fWidth(width)
373 , fSpacing(0)
374 , fHeight(height)
375 , fHalfLetterSpacing(0.0) {
376 size_t len = fOwner->getWhitespacesLength(fTextRange);
377 fIsWhiteSpaces = (len == this->fTextRange.width());
378}
379
380} // namespace textlayout
381} // namespace skia
382