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 namespace skia {
15 namespace textlayout {
16
Run(ParagraphImpl * owner,const SkShaper::RunHandler::RunInfo & info,size_t firstChar,SkScalar heightMultiplier,bool useHalfLeading,SkScalar baselineShift,size_t index,SkScalar offsetX)17 Run::Run(ParagraphImpl* owner,
18 const SkShaper::RunHandler::RunInfo& info,
19 size_t firstChar,
20 SkScalar heightMultiplier,
21 bool useHalfLeading,
22 SkScalar baselineShift,
23 size_t index,
24 SkScalar offsetX)
25 : fOwner(owner)
26 , fTextRange(firstChar + info.utf8Range.begin(), firstChar + info.utf8Range.end())
27 , fClusterRange(EMPTY_CLUSTERS)
28 , fFont(info.fFont)
29 , fClusterStart(firstChar)
30 , fHeightMultiplier(heightMultiplier)
31 , fUseHalfLeading(useHalfLeading)
32 , fBaselineShift(baselineShift)
33 {
34 fBidiLevel = info.fBidiLevel;
35 fAdvance = info.fAdvance;
36 fIndex = index;
37 fUtf8Range = info.utf8Range;
38 fOffset = SkVector::Make(offsetX, 0);
39 fGlyphs.push_back_n(info.glyphCount);
40 fBounds.push_back_n(info.glyphCount);
41 fPositions.push_back_n(info.glyphCount + 1);
42 fClusterIndexes.push_back_n(info.glyphCount + 1);
43 fShifts.push_back_n(info.glyphCount + 1, 0.0);
44 info.fFont.getMetrics(&fFontMetrics);
45
46 this->calculateMetrics();
47
48 fSpaced = false;
49 // To make edge cases easier:
50 fPositions[info.glyphCount] = fOffset + fAdvance;
51 fClusterIndexes[info.glyphCount] = this->leftToRight() ? info.utf8Range.end() : info.utf8Range.begin();
52 fEllipsis = false;
53 fPlaceholderIndex = std::numeric_limits<size_t>::max();
54 }
55
calculateMetrics()56 void Run::calculateMetrics() {
57 fCorrectAscent = fFontMetrics.fAscent - fFontMetrics.fLeading * 0.5;
58 fCorrectDescent = fFontMetrics.fDescent + fFontMetrics.fLeading * 0.5;
59 fCorrectLeading = 0;
60 if (SkScalarNearlyZero(fHeightMultiplier)) {
61 return;
62 }
63 const auto runHeight = fHeightMultiplier * fFont.getSize();
64 const auto fontIntrinsicHeight = fCorrectDescent - fCorrectAscent;
65 if (fUseHalfLeading) {
66 const auto extraLeading = (runHeight - fontIntrinsicHeight) / 2;
67 fCorrectAscent -= extraLeading;
68 fCorrectDescent += extraLeading;
69 } else {
70 const auto multiplier = runHeight / fontIntrinsicHeight;
71 fCorrectAscent *= multiplier;
72 fCorrectDescent *= multiplier;
73 }
74 // If we shift the baseline we need to make sure the shifted text fits the line
75 fCorrectAscent += fBaselineShift;
76 fCorrectDescent += fBaselineShift;
77 }
78
newRunBuffer()79 SkShaper::RunHandler::Buffer Run::newRunBuffer() {
80 return {fGlyphs.data(), fPositions.data(), nullptr, fClusterIndexes.data(), fOffset};
81 }
82
commit()83 void Run::commit() {
84 fFont.getBounds(fGlyphs.data(), fGlyphs.size(), fBounds.data(), nullptr);
85 }
86
copyTo(SkTextBlobBuilder & builder,size_t pos,size_t size) const87 void 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
findLimitingClusters(TextRange text) const111 std::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
130 // Adjust the text to grapheme edges so the first grapheme start is in the text and the last grapheme start is in the text
131 // It actually means that the first grapheme is entirely in the text and the last grapheme does not have to be
findLimitingGraphemes(TextRange text) const132 std::tuple<bool, TextIndex, TextIndex> Run::findLimitingGraphemes(TextRange text) const {
133 TextIndex start = fOwner->findNextGraphemeBoundary(text.start);
134 TextIndex end = fOwner->findNextGraphemeBoundary(text.end);
135 return std::make_tuple(true, start, end);
136 }
137
iterateThroughClusters(const ClusterVisitor & visitor)138 void Run::iterateThroughClusters(const ClusterVisitor& visitor) {
139
140 for (size_t index = 0; index < fClusterRange.width(); ++index) {
141 auto correctIndex = leftToRight() ? fClusterRange.start + index : fClusterRange.end - index - 1;
142 auto cluster = &fOwner->cluster(correctIndex);
143 visitor(cluster);
144 }
145 }
146
addSpacesAtTheEnd(SkScalar space,Cluster * cluster)147 SkScalar Run::addSpacesAtTheEnd(SkScalar space, Cluster* cluster) {
148 if (cluster->endPos() == cluster->startPos()) {
149 return 0;
150 }
151
152 fShifts[cluster->endPos() - 1] += space;
153 // Increment the run width
154 fSpaced = true;
155 fAdvance.fX += space;
156 // Increment the cluster width
157 cluster->space(space, space);
158
159 return space;
160 }
161
addSpacesEvenly(SkScalar space,Cluster * cluster)162 SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
163 // Offset all the glyphs in the cluster
164 SkScalar shift = 0;
165 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
166 fShifts[i] += shift;
167 shift += space;
168 }
169 if (this->size() == cluster->endPos()) {
170 // To make calculations easier
171 fShifts[cluster->endPos()] += shift;
172 }
173 // Increment the run width
174 fSpaced = true;
175 fAdvance.fX += shift;
176 // Increment the cluster width
177 cluster->space(shift, space);
178 cluster->setHalfLetterSpacing(space / 2);
179
180 return shift;
181 }
182
shift(const Cluster * cluster,SkScalar offset)183 void Run::shift(const Cluster* cluster, SkScalar offset) {
184 if (offset == 0) {
185 return;
186 }
187
188 fSpaced = true;
189 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
190 fShifts[i] += offset;
191 }
192 if (this->size() == cluster->endPos()) {
193 // To make calculations easier
194 fShifts[cluster->endPos()] += offset;
195 }
196 }
197
updateMetrics(InternalLineMetrics * endlineMetrics)198 void Run::updateMetrics(InternalLineMetrics* endlineMetrics) {
199
200 SkASSERT(isPlaceholder());
201 auto placeholderStyle = this->placeholderStyle();
202 // Difference between the placeholder baseline and the line bottom
203 SkScalar baselineAdjustment = 0;
204 switch (placeholderStyle->fBaseline) {
205 case TextBaseline::kAlphabetic:
206 break;
207
208 case TextBaseline::kIdeographic:
209 baselineAdjustment = endlineMetrics->deltaBaselines() / 2;
210 break;
211 }
212
213 auto height = placeholderStyle->fHeight;
214 auto offset = placeholderStyle->fBaselineOffset;
215
216 fFontMetrics.fLeading = 0;
217 switch (placeholderStyle->fAlignment) {
218 case PlaceholderAlignment::kBaseline:
219 fFontMetrics.fAscent = baselineAdjustment - offset;
220 fFontMetrics.fDescent = baselineAdjustment + height - offset;
221 break;
222
223 case PlaceholderAlignment::kAboveBaseline:
224 fFontMetrics.fAscent = baselineAdjustment - height;
225 fFontMetrics.fDescent = baselineAdjustment;
226 break;
227
228 case PlaceholderAlignment::kBelowBaseline:
229 fFontMetrics.fAscent = baselineAdjustment;
230 fFontMetrics.fDescent = baselineAdjustment + height;
231 break;
232
233 case PlaceholderAlignment::kTop:
234 fFontMetrics.fDescent = height + fFontMetrics.fAscent;
235 break;
236
237 case PlaceholderAlignment::kBottom:
238 fFontMetrics.fAscent = fFontMetrics.fDescent - height;
239 break;
240
241 case PlaceholderAlignment::kMiddle:
242 auto mid = (-fFontMetrics.fDescent - fFontMetrics.fAscent)/2.0;
243 fFontMetrics.fDescent = height/2.0 - mid;
244 fFontMetrics.fAscent = - height/2.0 - mid;
245 break;
246 }
247
248 this->calculateMetrics();
249
250 // Make sure the placeholder can fit the line
251 endlineMetrics->add(this);
252 }
253
sizeToChar(TextIndex ch) const254 SkScalar Cluster::sizeToChar(TextIndex ch) const {
255 if (ch < fTextRange.start || ch >= fTextRange.end) {
256 return 0;
257 }
258 auto shift = ch - fTextRange.start;
259 auto ratio = shift * 1.0 / fTextRange.width();
260
261 return SkDoubleToScalar(fWidth * ratio);
262 }
263
sizeFromChar(TextIndex ch) const264 SkScalar Cluster::sizeFromChar(TextIndex ch) const {
265 if (ch < fTextRange.start || ch >= fTextRange.end) {
266 return 0;
267 }
268 auto shift = fTextRange.end - ch - 1;
269 auto ratio = shift * 1.0 / fTextRange.width();
270
271 return SkDoubleToScalar(fWidth * ratio);
272 }
273
roundPos(SkScalar s) const274 size_t Cluster::roundPos(SkScalar s) const {
275 auto ratio = (s * 1.0) / fWidth;
276 return sk_double_floor2int(ratio * size());
277 }
278
trimmedWidth(size_t pos) const279 SkScalar Cluster::trimmedWidth(size_t pos) const {
280 // Find the width until the pos and return the min between trimmedWidth and the width(pos)
281 // We don't have to take in account cluster shift since it's the same for 0 and for pos
282 auto& run = fOwner->run(fRunIndex);
283 return std::min(run.positionX(pos) - run.positionX(fStart), fWidth);
284 }
285
positionX(size_t pos) const286 SkScalar Run::positionX(size_t pos) const {
287 return posX(pos) + fShifts[pos] +
288 (fJustificationShifts.empty() ? 0 : fJustificationShifts[pos].fY);
289 }
290
placeholderStyle() const291 PlaceholderStyle* Run::placeholderStyle() const {
292 if (isPlaceholder()) {
293 return &fOwner->placeholders()[fPlaceholderIndex].fStyle;
294 } else {
295 return nullptr;
296 }
297 }
298
runOrNull() const299 Run* Cluster::runOrNull() const {
300 if (fRunIndex >= fOwner->runs().size()) {
301 return nullptr;
302 }
303 return &fOwner->run(fRunIndex);
304 }
305
run() const306 Run& Cluster::run() const {
307 SkASSERT(fRunIndex < fOwner->runs().size());
308 return fOwner->run(fRunIndex);
309 }
310
font() const311 SkFont Cluster::font() const {
312 SkASSERT(fRunIndex < fOwner->runs().size());
313 return fOwner->run(fRunIndex).font();
314 }
315
isSoftBreak() const316 bool Cluster::isSoftBreak() const {
317 return fOwner->codeUnitHasProperty(fTextRange.end, CodeUnitFlags::kSoftLineBreakBefore);
318 }
319
isGraphemeBreak() const320 bool Cluster::isGraphemeBreak() const {
321 return fOwner->codeUnitHasProperty(fTextRange.end, CodeUnitFlags::kGraphemeStart);
322 }
323 } // namespace textlayout
324 } // namespace skia
325