xref: /aosp_15_r20/external/skia/modules/skplaintexteditor/src/shape.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 // Copyright 2019 Google LLC.
2 // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3 
4 #include "modules/skplaintexteditor/src/shape.h"
5 
6 #include "include/core/SkFont.h"
7 #include "include/core/SkFontMetrics.h"
8 #include "include/core/SkFontMgr.h"
9 #include "include/core/SkPoint.h"
10 #include "include/core/SkRefCnt.h"
11 #include "include/core/SkScalar.h"
12 #include "include/core/SkString.h"
13 #include "include/core/SkTextBlob.h"
14 #include "include/core/SkTypes.h"
15 #include "include/private/base/SkTFitsIn.h"
16 #include "modules/skplaintexteditor/src/word_boundaries.h"
17 #include "modules/skshaper/include/SkShaper.h"
18 #include "src/base/SkUTF.h"
19 #include "src/core/SkTextBlobPriv.h"
20 
21 #if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && defined(SK_SHAPER_UNICODE_AVAILABLE)
22 #include "modules/skshaper/include/SkShaper_harfbuzz.h"
23 #include "modules/skshaper/include/SkShaper_skunicode.h"
24 #include "modules/skunicode/include/SkUnicode.h"
25 #else
26 class SkUnicode;
27 #endif
28 
29 #if defined(SK_UNICODE_ICU_IMPLEMENTATION)
30 #include "modules/skunicode/include/SkUnicode_icu.h"
31 #endif
32 #if defined(SK_UNICODE_LIBGRAPHEME_IMPLEMENTATION)
33 #include "modules/skunicode/include/SkUnicode_libgrapheme.h"
34 #endif
35 #if defined(SK_UNICODE_ICU4X_IMPLEMENTATION)
36 #include "modules/skunicode/include/SkUnicode_icu4x.h"
37 #endif
38 
39 #include <cfloat>
40 #include <climits>
41 #include <cstring>
42 
43 using namespace SkPlainTextEditor;
44 
45 namespace {
46 class RunHandler final : public SkShaper::RunHandler {
47 public:
RunHandler(const char * utf8Text,size_t)48     RunHandler(const char* utf8Text, size_t) : fUtf8Text(utf8Text) {}
49     using RunCallback = void (*)(void* context,
50                                  const char* utf8Text,
51                                   size_t utf8TextBytes,
52                                   size_t glyphCount,
53                                   const SkGlyphID* glyphs,
54                                   const SkPoint* positions,
55                                   const uint32_t* clusters,
56                                   const SkFont& font);
setRunCallback(RunCallback f,void * context)57     void setRunCallback(RunCallback f, void* context) {
58         fCallbackContext = context;
59         fCallbackFunction = f;
60     }
61 
62     sk_sp<SkTextBlob> makeBlob();
endPoint() const63     SkPoint endPoint() const { return fOffset; }
finalPosition() const64     SkPoint finalPosition() const { return fCurrentPosition; }
65 
66     void beginLine() override;
67     void runInfo(const RunInfo&) override;
68     void commitRunInfo() override;
69     SkShaper::RunHandler::Buffer runBuffer(const RunInfo&) override;
70     void commitRunBuffer(const RunInfo&) override;
71     void commitLine() override;
72 
lineEndOffsets() const73     const std::vector<size_t>& lineEndOffsets() const { return fLineEndOffsets; }
74 
finalRect(const SkFont & font) const75     SkRect finalRect(const SkFont& font) const {
76         if (0 == fMaxRunAscent || 0 == fMaxRunDescent) {
77             SkFontMetrics metrics;
78             font.getMetrics(&metrics);
79             return {fCurrentPosition.x(),
80                     fCurrentPosition.y(),
81                     fCurrentPosition.x() + font.getSize(),
82                     fCurrentPosition.y() + metrics.fDescent - metrics.fAscent};
83         } else {
84             return {fCurrentPosition.x(),
85                     fCurrentPosition.y() + fMaxRunAscent,
86                     fCurrentPosition.x() + font.getSize(),
87                     fCurrentPosition.y() + fMaxRunDescent};
88         }
89     }
90 
91 
92 private:
93     SkTextBlobBuilder fBuilder;
94     std::vector<size_t> fLineEndOffsets;
95     const SkGlyphID* fCurrentGlyphs = nullptr;
96     const SkPoint* fCurrentPoints = nullptr;
97     void* fCallbackContext = nullptr;
98     RunCallback fCallbackFunction = nullptr;
99     char const * const fUtf8Text;
100     size_t fTextOffset = 0;
101     uint32_t* fClusters = nullptr;
102     int fClusterOffset = 0;
103     int fGlyphCount = 0;
104     SkScalar fMaxRunAscent = 0;
105     SkScalar fMaxRunDescent = 0;
106     SkScalar fMaxRunLeading = 0;
107     SkPoint fCurrentPosition = {0, 0};
108     SkPoint fOffset = {0, 0};
109 };
110 
111 // TODO(kjlubick,jlavrova) Remove these defines by having clients register something or somehow
112 // plumbing this all into the animation builder factories.
113 #if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && defined(SK_SHAPER_UNICODE_AVAILABLE)
get_unicode()114 sk_sp<SkUnicode> get_unicode() {
115 #if defined(SK_UNICODE_ICU_IMPLEMENTATION)
116     if (auto unicode = SkUnicodes::ICU::Make()) {
117         return unicode;
118     }
119 #endif  // defined(SK_UNICODE_ICU_IMPLEMENTATION)
120 #if defined(SK_UNICODE_LIBGRAPHEME_IMPLEMENTATION)
121     if (auto unicode = SkUnicodes::Libgrapheme::Make()) {
122         return unicode;
123     }
124 #endif
125 #if defined(SK_UNICODE_ICU4X_IMPLEMENTATION)
126     if (auto unicode = SkUnicodes::ICU4X::Make()) {
127         return unicode;
128     }
129 #endif
130     return nullptr;
131 }
132 #endif
133 }  // namespace
134 
beginLine()135 void RunHandler::beginLine() {
136     fCurrentPosition = fOffset;
137     fMaxRunAscent = 0;
138     fMaxRunDescent = 0;
139     fMaxRunLeading = 0;
140 }
141 
runInfo(const SkShaper::RunHandler::RunInfo & info)142 void RunHandler::runInfo(const SkShaper::RunHandler::RunInfo& info) {
143     SkFontMetrics metrics;
144     info.fFont.getMetrics(&metrics);
145     fMaxRunAscent = std::min(fMaxRunAscent, metrics.fAscent);
146     fMaxRunDescent = std::max(fMaxRunDescent, metrics.fDescent);
147     fMaxRunLeading = std::max(fMaxRunLeading, metrics.fLeading);
148 }
149 
commitRunInfo()150 void RunHandler::commitRunInfo() {
151     fCurrentPosition.fY -= fMaxRunAscent;
152 }
153 
runBuffer(const RunInfo & info)154 SkShaper::RunHandler::Buffer RunHandler::runBuffer(const RunInfo& info) {
155     int glyphCount = SkTFitsIn<int>(info.glyphCount) ? info.glyphCount : INT_MAX;
156     int utf8RangeSize = SkTFitsIn<int>(info.utf8Range.size()) ? info.utf8Range.size() : INT_MAX;
157 
158     const auto& runBuffer = fBuilder.allocRunTextPos(info.fFont, glyphCount, utf8RangeSize);
159     fCurrentGlyphs = runBuffer.glyphs;
160     fCurrentPoints = runBuffer.points();
161 
162     if (runBuffer.utf8text && fUtf8Text) {
163         memcpy(runBuffer.utf8text, fUtf8Text + info.utf8Range.begin(), utf8RangeSize);
164     }
165     fClusters = runBuffer.clusters;
166     fGlyphCount = glyphCount;
167     fClusterOffset = info.utf8Range.begin();
168 
169     return {runBuffer.glyphs,
170             runBuffer.points(),
171             nullptr,
172             runBuffer.clusters,
173             fCurrentPosition};
174 }
175 
commitRunBuffer(const RunInfo & info)176 void RunHandler::commitRunBuffer(const RunInfo& info) {
177     // for (size_t i = 0; i < info.glyphCount; ++i) {
178     //     SkASSERT(fClusters[i] >= info.utf8Range.begin());
179     //     // this fails for khmer example.
180     //     SkASSERT(fClusters[i] <  info.utf8Range.end());
181     // }
182     if (fCallbackFunction) {
183         fCallbackFunction(fCallbackContext,
184                           fUtf8Text,
185                           info.utf8Range.end(),
186                           info.glyphCount,
187                           fCurrentGlyphs,
188                           fCurrentPoints,
189                           fClusters,
190                           info.fFont);
191     }
192     SkASSERT(0 <= fClusterOffset);
193     for (int i = 0; i < fGlyphCount; ++i) {
194         SkASSERT(fClusters[i] >= (unsigned)fClusterOffset);
195         fClusters[i] -= fClusterOffset;
196     }
197     fCurrentPosition += info.fAdvance;
198     fTextOffset = std::max(fTextOffset, info.utf8Range.end());
199 }
200 
commitLine()201 void RunHandler::commitLine() {
202     if (fLineEndOffsets.empty() || fTextOffset > fLineEndOffsets.back()) {
203         // Ensure that fLineEndOffsets is monotonic.
204         fLineEndOffsets.push_back(fTextOffset);
205     }
206     fOffset += { 0, fMaxRunDescent + fMaxRunLeading - fMaxRunAscent };
207 }
208 
makeBlob()209 sk_sp<SkTextBlob> RunHandler::makeBlob() {
210     return fBuilder.make();
211 }
212 
selection_box(const SkFontMetrics & metrics,float advance,SkPoint pos)213 static SkRect selection_box(const SkFontMetrics& metrics,
214                             float advance,
215                             SkPoint pos) {
216     if (fabsf(advance) < 1.0f) {
217         advance = copysignf(1.0f, advance);
218     }
219     return SkRect{pos.x(),
220                   pos.y() + metrics.fAscent,
221                   pos.x() + advance,
222                   pos.y() + metrics.fDescent}.makeSorted();
223 }
224 
set_character_bounds(void * context,const char * utf8Text,size_t utf8TextBytes,size_t glyphCount,const SkGlyphID * glyphs,const SkPoint * positions,const uint32_t * clusters,const SkFont & font)225 static void set_character_bounds(void* context,
226                                  const char* utf8Text,
227                                  size_t utf8TextBytes,
228                                  size_t glyphCount,
229                                  const SkGlyphID* glyphs,
230                                  const SkPoint* positions,
231                                  const uint32_t* clusters,
232                                  const SkFont& font)
233 {
234     SkASSERT(context);
235     SkASSERT(glyphCount > 0);
236     SkRect* cursors = (SkRect*)context;
237 
238     SkFontMetrics metrics;
239     font.getMetrics(&metrics);
240     std::unique_ptr<float[]> advances(new float[glyphCount]);
241     font.getWidths(glyphs, glyphCount, advances.get());
242 
243     // Loop over each cluster in this run.
244     size_t clusterStart = 0;
245     for (size_t glyphIndex = 0; glyphIndex < glyphCount; ++glyphIndex) {
246         if (glyphIndex + 1 < glyphCount  // more glyphs
247             && clusters[glyphIndex] == clusters[glyphIndex + 1]) {
248             continue; // multi-glyph cluster
249         }
250         unsigned textBegin = clusters[glyphIndex];
251         unsigned textEnd = utf8TextBytes;
252         for (size_t i = 0; i < glyphCount; ++i) {
253             if (clusters[i] >= textEnd) {
254                 textEnd = clusters[i] + 1;
255             }
256         }
257         for (size_t i = 0; i < glyphCount; ++i) {
258             if (clusters[i] > textBegin && clusters[i] < textEnd) {
259                 textEnd = clusters[i];
260                 if (textEnd == textBegin + 1) { break; }
261             }
262         }
263         SkASSERT(glyphIndex + 1 > clusterStart);
264         unsigned clusterGlyphCount = glyphIndex + 1 - clusterStart;
265         const SkPoint* clusterGlyphPositions = &positions[clusterStart];
266         const float* clusterAdvances = &advances[clusterStart];
267         clusterStart = glyphIndex + 1;  // for next loop
268 
269         SkRect clusterBox = selection_box(metrics, clusterAdvances[0], clusterGlyphPositions[0]);
270         for (unsigned i = 1; i < clusterGlyphCount; ++i) { // multiple glyphs
271             clusterBox.join(selection_box(metrics, clusterAdvances[i], clusterGlyphPositions[i]));
272         }
273         if (textBegin + 1 == textEnd) {  // single byte, fast path.
274             cursors[textBegin] = clusterBox;
275             continue;
276         }
277         int textCount = textEnd - textBegin;
278         int codePointCount = SkUTF::CountUTF8(utf8Text + textBegin, textCount);
279         if (codePointCount == 1) {  // single codepoint, fast path.
280             cursors[textBegin] = clusterBox;
281             continue;
282         }
283 
284         float width = clusterBox.width() / codePointCount;
285         SkASSERT(width > 0);
286         const char* ptr = utf8Text + textBegin;
287         const char* end = utf8Text + textEnd;
288         float x = clusterBox.left();
289         while (ptr < end) {  // for each codepoint in cluster
290             const char* nextPtr = ptr;
291             SkUTF::NextUTF8(&nextPtr, end);
292             int firstIndex = ptr - utf8Text;
293             float nextX = x + width;
294             cursors[firstIndex] = SkRect{x, clusterBox.top(), nextX, clusterBox.bottom()};
295             x = nextX;
296             ptr = nextPtr;
297         }
298     }
299 }
300 
Shape(const char * utf8Text,size_t textByteLen,const SkFont & font,sk_sp<SkFontMgr> fontMgr,const char * locale,float width)301 ShapeResult SkPlainTextEditor::Shape(const char* utf8Text,
302                                      size_t textByteLen,
303                                      const SkFont& font,
304                                      sk_sp<SkFontMgr> fontMgr,
305                                      const char* locale,
306                                      float width)
307 {
308     ShapeResult result;
309     if (SkUTF::CountUTF8(utf8Text, textByteLen) < 0) {
310         utf8Text = nullptr;
311         textByteLen = 0;
312     }
313     std::unique_ptr<SkShaper> shaper = nullptr;
314 #if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && defined(SK_SHAPER_UNICODE_AVAILABLE)
315     auto unicode = get_unicode();
316     shaper = SkShapers::HB::ShaperDrivenWrapper(unicode, fontMgr);
317 #else
318     shaper = SkShapers::Primitive::PrimitiveText();
319 #endif
320     SkASSERT(shaper);
321 
322     float height = font.getSpacing();
323     RunHandler runHandler(utf8Text, textByteLen);
324     if (textByteLen) {
325         result.glyphBounds.resize(textByteLen);
326         for (SkRect& c : result.glyphBounds) {
327             c = SkRect{-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX};
328         }
329         runHandler.setRunCallback(set_character_bounds, result.glyphBounds.data());
330 
331         static constexpr uint8_t kBidiLevelLTR = 0;
332         std::unique_ptr<SkShaper::BiDiRunIterator> bidi = nullptr;
333 #if defined(SK_SHAPER_HARFBUZZ_AVAILABLE) && defined(SK_SHAPER_UNICODE_AVAILABLE)
334         bidi = SkShapers::unicode::BidiRunIterator(
335                 unicode, utf8Text, textByteLen, kBidiLevelLTR);
336 #endif
337         if (!bidi) {
338             bidi = std::make_unique<SkShaper::TrivialBiDiRunIterator>(kBidiLevelLTR, textByteLen);
339         }
340         SkASSERT(bidi);
341 
342         std::unique_ptr<SkShaper::LanguageRunIterator> language =
343                 SkShaper::MakeStdLanguageRunIterator(utf8Text, textByteLen);
344         SkASSERT(language);
345 
346         std::unique_ptr<SkShaper::ScriptRunIterator> script =
347                 SkShaper::MakeScriptRunIterator(utf8Text, textByteLen, SkSetFourByteTag('Z','z','z','z'));
348         SkASSERT(script);
349 
350         std::unique_ptr<SkShaper::FontRunIterator> fontRuns =
351                 SkShaper::MakeFontMgrRunIterator(utf8Text, textByteLen, font, fontMgr);
352         SkASSERT(fontRuns);
353 
354         shaper->shape(utf8Text,
355                       textByteLen,
356                       *fontRuns,
357                       *bidi,
358                       *script,
359                       *language,
360                       nullptr,
361                       0,
362                       width,
363                       &runHandler);
364         if (runHandler.lineEndOffsets().size() > 1) {
365             result.lineBreakOffsets = runHandler.lineEndOffsets();
366             SkASSERT(result.lineBreakOffsets.size() > 0);
367             result.lineBreakOffsets.pop_back();
368         }
369         height = std::max(height, runHandler.endPoint().y());
370         result.blob = runHandler.makeBlob();
371     }
372     result.glyphBounds.push_back(runHandler.finalRect(font));
373     result.verticalAdvance = (int)ceilf(height);
374     result.wordBreaks = GetUtf8WordBoundaries(utf8Text, textByteLen, locale);
375     return result;
376 }
377