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