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,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 size_t index,
23 SkScalar offsetX)
24 : fOwner(owner)
25 , fTextRange(firstChar + info.utf8Range.begin(), firstChar + info.utf8Range.end())
26 , fClusterRange(EMPTY_CLUSTERS)
27 , fFont(info.fFont)
28 , fClusterStart(firstChar)
29 , fHeightMultiplier(heightMultiplier)
30 , fUseHalfLeading(useHalfLeading)
31 {
32 fBidiLevel = info.fBidiLevel;
33 fAdvance = info.fAdvance;
34 fIndex = index;
35 fUtf8Range = info.utf8Range;
36 fOffset = SkVector::Make(offsetX, 0);
37 fGlyphs.push_back_n(info.glyphCount);
38 fBounds.push_back_n(info.glyphCount);
39 fPositions.push_back_n(info.glyphCount + 1);
40 fClusterIndexes.push_back_n(info.glyphCount + 1);
41 fShifts.push_back_n(info.glyphCount + 1, 0.0);
42 info.fFont.getMetrics(&fFontMetrics);
43
44 this->calculateMetrics();
45
46 fSpaced = false;
47 // To make edge cases easier:
48 fPositions[info.glyphCount] = fOffset + fAdvance;
49 fClusterIndexes[info.glyphCount] = this->leftToRight() ? info.utf8Range.end() : info.utf8Range.begin();
50 fEllipsis = false;
51 fPlaceholderIndex = std::numeric_limits<size_t>::max();
52 }
53
calculateMetrics()54 void Run::calculateMetrics() {
55 fCorrectAscent = fFontMetrics.fAscent - fFontMetrics.fLeading * 0.5;
56 fCorrectDescent = fFontMetrics.fDescent + fFontMetrics.fLeading * 0.5;
57 fCorrectLeading = 0;
58 if (SkScalarNearlyZero(fHeightMultiplier)) {
59 return;
60 }
61 const auto runHeight = fHeightMultiplier * fFont.getSize();
62 const auto fontIntrinsicHeight = fCorrectDescent - fCorrectAscent;
63 if (fUseHalfLeading) {
64 const auto extraLeading = (runHeight - fontIntrinsicHeight) / 2;
65 fCorrectAscent -= extraLeading;
66 fCorrectDescent += extraLeading;
67 } else {
68 const auto multiplier = runHeight / fontIntrinsicHeight;
69 fCorrectAscent *= multiplier;
70 fCorrectDescent *= multiplier;
71 }
72 }
73
newRunBuffer()74 SkShaper::RunHandler::Buffer Run::newRunBuffer() {
75 return {fGlyphs.data(), fPositions.data(), nullptr, fClusterIndexes.data(), fOffset};
76 }
77
commit()78 void Run::commit() {
79 fFont.getBounds(fGlyphs.data(), fGlyphs.size(), fBounds.data(), nullptr);
80 }
81
copyTo(SkTextBlobBuilder & builder,size_t pos,size_t size) const82 void Run::copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size) const {
83 SkASSERT(pos + size <= this->size());
84 const auto& blobBuffer = builder.allocRunPos(fFont, SkToInt(size));
85 sk_careful_memcpy(blobBuffer.glyphs, fGlyphs.data() + pos, size * sizeof(SkGlyphID));
86
87 if (!fSpaced && fJustificationShifts.empty()) {
88 sk_careful_memcpy(blobBuffer.points(), fPositions.data() + pos, size * sizeof(SkPoint));
89 } else {
90 for (size_t i = 0; i < size; ++i) {
91 auto point = fPositions[i + pos];
92 if (fSpaced) {
93 point.fX += fShifts[i + pos];
94 }
95 if (!fJustificationShifts.empty()) {
96 point.fX += fJustificationShifts[i + pos].fX;
97 }
98 blobBuffer.points()[i] = point;
99 }
100 }
101 }
102
103 // Find a cluster range from text range (within one run)
104 // Cluster range is normalized ([start:end) start < end regardless of TextDirection
105 // Boolean value in triple indicates whether the cluster range was found or not
findLimitingClusters(TextRange text) const106 std::tuple<bool, ClusterIndex, ClusterIndex> Run::findLimitingClusters(TextRange text) const {
107 if (text.width() == 0) {
108 // Special Flutter case for "\n" and "...\n"
109 if (text.end > this->fTextRange.start) {
110 ClusterIndex index = fOwner->clusterIndex(text.end - 1);
111 return std::make_tuple(true, index, index);
112 } else {
113 return std::make_tuple(false, 0, 0);
114 }
115 }
116
117 ClusterIndex startIndex = fOwner->clusterIndex(text.start);
118 ClusterIndex endIndex = fOwner->clusterIndex(text.end - 1);
119 if (!leftToRight()) {
120 std::swap(startIndex, endIndex);
121 }
122 return std::make_tuple(startIndex != fClusterRange.end && endIndex != fClusterRange.end, startIndex, endIndex);
123 }
124
125 // Adjust the text to grapheme edges so the first grapheme start is in the text and the last grapheme start is in the text
126 // It actually means that the first grapheme is entirely in the text and the last grapheme does not have to be
findLimitingGraphemes(TextRange text) const127 std::tuple<bool, TextIndex, TextIndex> Run::findLimitingGraphemes(TextRange text) const {
128 TextIndex start = fOwner->findNextGraphemeBoundary(text.start);
129 TextIndex end = fOwner->findNextGraphemeBoundary(text.end);
130 return std::make_tuple(true, start, end);
131 }
132
iterateThroughClusters(const ClusterVisitor & visitor)133 void Run::iterateThroughClusters(const ClusterVisitor& visitor) {
134
135 for (size_t index = 0; index < fClusterRange.width(); ++index) {
136 auto correctIndex = leftToRight() ? fClusterRange.start + index : fClusterRange.end - index - 1;
137 auto cluster = &fOwner->cluster(correctIndex);
138 visitor(cluster);
139 }
140 }
141
addSpacesAtTheEnd(SkScalar space,Cluster * cluster)142 SkScalar Run::addSpacesAtTheEnd(SkScalar space, Cluster* cluster) {
143 if (cluster->endPos() == cluster->startPos()) {
144 return 0;
145 }
146
147 fShifts[cluster->endPos() - 1] += space;
148 // Increment the run width
149 fSpaced = true;
150 fAdvance.fX += space;
151 // Increment the cluster width
152 cluster->space(space, space);
153
154 return space;
155 }
156
addSpacesEvenly(SkScalar space,Cluster * cluster)157 SkScalar Run::addSpacesEvenly(SkScalar space, Cluster* cluster) {
158 // Offset all the glyphs in the cluster
159 SkScalar shift = 0;
160 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
161 fShifts[i] += shift;
162 shift += space;
163 }
164 if (this->size() == cluster->endPos()) {
165 // To make calculations easier
166 fShifts[cluster->endPos()] += shift;
167 }
168 // Increment the run width
169 fSpaced = true;
170 fAdvance.fX += shift;
171 // Increment the cluster width
172 cluster->space(shift, space);
173 cluster->setHalfLetterSpacing(space / 2);
174
175 return shift;
176 }
177
shift(const Cluster * cluster,SkScalar offset)178 void Run::shift(const Cluster* cluster, SkScalar offset) {
179 if (offset == 0) {
180 return;
181 }
182
183 fSpaced = true;
184 for (size_t i = cluster->startPos(); i < cluster->endPos(); ++i) {
185 fShifts[i] += offset;
186 }
187 if (this->size() == cluster->endPos()) {
188 // To make calculations easier
189 fShifts[cluster->endPos()] += offset;
190 }
191 }
192
updateMetrics(InternalLineMetrics * endlineMetrics)193 void Run::updateMetrics(InternalLineMetrics* endlineMetrics) {
194
195 SkASSERT(isPlaceholder());
196 auto placeholderStyle = this->placeholderStyle();
197 // Difference between the placeholder baseline and the line bottom
198 SkScalar baselineAdjustment = 0;
199 switch (placeholderStyle->fBaseline) {
200 case TextBaseline::kAlphabetic:
201 break;
202
203 case TextBaseline::kIdeographic:
204 baselineAdjustment = endlineMetrics->deltaBaselines() / 2;
205 break;
206 }
207
208 auto height = placeholderStyle->fHeight;
209 auto offset = placeholderStyle->fBaselineOffset;
210
211 fFontMetrics.fLeading = 0;
212 switch (placeholderStyle->fAlignment) {
213 case PlaceholderAlignment::kBaseline:
214 fFontMetrics.fAscent = baselineAdjustment - offset;
215 fFontMetrics.fDescent = baselineAdjustment + height - offset;
216 break;
217
218 case PlaceholderAlignment::kAboveBaseline:
219 fFontMetrics.fAscent = baselineAdjustment - height;
220 fFontMetrics.fDescent = baselineAdjustment;
221 break;
222
223 case PlaceholderAlignment::kBelowBaseline:
224 fFontMetrics.fAscent = baselineAdjustment;
225 fFontMetrics.fDescent = baselineAdjustment + height;
226 break;
227
228 case PlaceholderAlignment::kTop:
229 fFontMetrics.fDescent = height + fFontMetrics.fAscent;
230 break;
231
232 case PlaceholderAlignment::kBottom:
233 fFontMetrics.fAscent = fFontMetrics.fDescent - height;
234 break;
235
236 case PlaceholderAlignment::kMiddle:
237 auto mid = (-fFontMetrics.fDescent - fFontMetrics.fAscent)/2.0;
238 fFontMetrics.fDescent = height/2.0 - mid;
239 fFontMetrics.fAscent = - height/2.0 - mid;
240 break;
241 }
242
243 this->calculateMetrics();
244
245 // Make sure the placeholder can fit the line
246 endlineMetrics->add(this);
247 }
248
sizeToChar(TextIndex ch) const249 SkScalar Cluster::sizeToChar(TextIndex ch) const {
250 if (ch < fTextRange.start || ch >= fTextRange.end) {
251 return 0;
252 }
253 auto shift = ch - fTextRange.start;
254 auto ratio = shift * 1.0 / fTextRange.width();
255
256 return SkDoubleToScalar(fWidth * ratio);
257 }
258
sizeFromChar(TextIndex ch) const259 SkScalar Cluster::sizeFromChar(TextIndex ch) const {
260 if (ch < fTextRange.start || ch >= fTextRange.end) {
261 return 0;
262 }
263 auto shift = fTextRange.end - ch - 1;
264 auto ratio = shift * 1.0 / fTextRange.width();
265
266 return SkDoubleToScalar(fWidth * ratio);
267 }
268
roundPos(SkScalar s) const269 size_t Cluster::roundPos(SkScalar s) const {
270 auto ratio = (s * 1.0) / fWidth;
271 return sk_double_floor2int(ratio * size());
272 }
273
trimmedWidth(size_t pos) const274 SkScalar Cluster::trimmedWidth(size_t pos) const {
275 // Find the width until the pos and return the min between trimmedWidth and the width(pos)
276 // We don't have to take in account cluster shift since it's the same for 0 and for pos
277 auto& run = fOwner->run(fRunIndex);
278 return std::min(run.positionX(pos) - run.positionX(fStart), fWidth);
279 }
280
positionX(size_t pos) const281 SkScalar Run::positionX(size_t pos) const {
282 return posX(pos) + fShifts[pos] +
283 (fJustificationShifts.empty() ? 0 : fJustificationShifts[pos].fY);
284 }
285
placeholderStyle() const286 PlaceholderStyle* Run::placeholderStyle() const {
287 if (isPlaceholder()) {
288 return &fOwner->placeholders()[fPlaceholderIndex].fStyle;
289 } else {
290 return nullptr;
291 }
292 }
293
runOrNull() const294 Run* Cluster::runOrNull() const {
295 if (fRunIndex >= fOwner->runs().size()) {
296 return nullptr;
297 }
298 return &fOwner->run(fRunIndex);
299 }
300
run() const301 Run& Cluster::run() const {
302 SkASSERT(fRunIndex < fOwner->runs().size());
303 return fOwner->run(fRunIndex);
304 }
305
font() const306 SkFont Cluster::font() const {
307 SkASSERT(fRunIndex < fOwner->runs().size());
308 return fOwner->run(fRunIndex).font();
309 }
310
isSoftBreak() const311 bool Cluster::isSoftBreak() const {
312 return fOwner->codeUnitHasProperty(fTextRange.end, CodeUnitFlags::kSoftLineBreakBefore);
313 }
314
isGraphemeBreak() const315 bool Cluster::isGraphemeBreak() const {
316 return fOwner->codeUnitHasProperty(fTextRange.end, CodeUnitFlags::kGraphemeStart);
317 }
318 } // namespace textlayout
319 } // namespace skia
320