xref: /aosp_15_r20/external/skia/modules/skparagraph/gm/simple_gm.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2020 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "gm/gm.h"
9 #include "include/core/SkCanvas.h"
10 #include "include/core/SkColor.h"
11 #include "include/core/SkFont.h"
12 #include "include/core/SkFontTypes.h"
13 #include "include/core/SkPaint.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkScalar.h"
17 #include "include/core/SkShader.h"
18 #include "include/core/SkSize.h"
19 #include "include/core/SkString.h"
20 #include "include/core/SkTypeface.h"
21 #include "modules/skparagraph/include/Paragraph.h"
22 #include "modules/skparagraph/src/ParagraphBuilderImpl.h"
23 #include "tools/ToolUtils.h"
24 #include "tools/fonts/FontToolUtils.h"
25 
26 static const char* gSpeech = "Five score years ago, a great American, in whose symbolic shadow we stand today, signed the Emancipation Proclamation. This momentous decree came as a great beacon light of hope to millions of Negro slaves who had been seared in the flames of withering injustice. It came as a joyous daybreak to end the long night of their captivity.";
27 
28 #if defined(SK_UNICODE_ICU_IMPLEMENTATION)
29 #include "modules/skunicode/include/SkUnicode_icu.h"
30 #endif
31 
32 #if defined(SK_UNICODE_LIBGRAPHEME_IMPLEMENTATION)
33 #include "modules/skunicode/include/SkUnicode_libgrapheme.h"
34 #endif
35 
36 #if defined(SK_UNICODE_ICU4X_IMPLEMENTATION)
37 #include "modules/skunicode/include/SkUnicode_icu4x.h"
38 #endif
39 
40 namespace {
41 enum ParaFlags {
42     kTimeLayout     = 1 << 0,
43     kUseUnderline   = 1 << 1,
44     kShowVisitor    = 1 << 2,
45 };
46 
get_unicode()47 sk_sp<SkUnicode> get_unicode() {
48 #if defined(SK_UNICODE_ICU_IMPLEMENTATION)
49     if (auto unicode = SkUnicodes::ICU::Make()) {
50         return unicode;
51     }
52 #endif
53 #if defined(SK_UNICODE_LIBGRAPHEME_IMPLEMENTATION)
54     if (auto unicode = SkUnicodes::Libgrapheme::Make()) {
55         return unicode;
56     }
57 #endif
58 #if defined(SK_UNICODE_ICU4X_IMPLEMENTATION)
59     if (auto unicode = SkUnicodes::ICU4X::Make()) {
60         return unicode;
61     }
62 #endif
63     return nullptr;
64 }
65 }  // namespace
66 
67 // TODO: Make it work with ALL possible SkUnicodes
68 class ParagraphGM : public skiagm::GM {
69     std::unique_ptr<skia::textlayout::Paragraph> fPara;
70     const unsigned fFlags;
71 
72 public:
ParagraphGM(unsigned flags)73     ParagraphGM(unsigned flags) : fFlags(flags) {}
74 
buildParagraph()75     void buildParagraph() {
76         skia::textlayout::TextStyle style;
77         style.setForegroundColor(SkPaint());
78         style.setFontFamilies({SkString("sans-serif")});
79         style.setFontSize(30);
80 
81         if (fFlags & kUseUnderline) {
82             style.setDecoration(skia::textlayout::TextDecoration::kUnderline);
83             style.setDecorationMode(skia::textlayout::TextDecorationMode::kThrough);
84             style.setDecorationColor(SK_ColorBLACK);
85             style.setDecorationThicknessMultiplier(2);
86         }
87 
88         skia::textlayout::ParagraphStyle paraStyle;
89         paraStyle.setTextStyle(style);
90 
91         sk_sp<SkFontMgr> fontmgr = ToolUtils::TestFontMgr();
92         if (fontmgr->countFamilies() == 0) {
93             fPara = nullptr;
94             return;
95         }
96         auto collection = sk_make_sp<skia::textlayout::FontCollection>();
97         collection->setDefaultFontManager(std::move(fontmgr));
98 
99         auto unicode = get_unicode();
100         if (!unicode) {
101             fPara = nullptr;
102             return;
103         }
104         auto builder = skia::textlayout::ParagraphBuilderImpl::make(
105                 paraStyle, collection, unicode);
106         if (nullptr == builder) {
107             fPara = nullptr;
108             return;
109         }
110 
111         builder->addText(gSpeech, strlen(gSpeech));
112 
113         fPara = builder->Build();
114         fPara->layout(400);
115     }
116 
117 protected:
onOnceBeforeDraw()118     void onOnceBeforeDraw() override {
119         this->buildParagraph();
120     }
121 
getName() const122     SkString getName() const override {
123         SkString name;
124         name.printf("paragraph%s_%s",
125                     fFlags & kTimeLayout   ? "_layout"    : "",
126                     fFlags & kUseUnderline ? "_underline" : "");
127         if (fFlags & kShowVisitor) {
128             name.append("_visitor");
129         }
130         return name;
131     }
132 
getISize()133     SkISize getISize() override {
134         if (fFlags & kShowVisitor) {
135             return SkISize::Make(810, 420);
136         }
137         return SkISize::Make(412, 420);
138     }
139 
drawFromVisitor(SkCanvas * canvas,skia::textlayout::Paragraph * para) const140     void drawFromVisitor(SkCanvas* canvas, skia::textlayout::Paragraph* para) const {
141         SkPaint p, p2;
142         p.setColor(0xFF0000FF);
143         p2.setColor(0xFFFF0000);
144         p2.setStrokeWidth(4);
145         p2.setStrokeCap(SkPaint::kSquare_Cap);
146         SkPaint underp;
147         underp.setStroke(true);
148         underp.setStrokeWidth(2);
149         underp.setAntiAlias(true);
150         underp.setColor(p.getColor());
151         const SkScalar GAP = 2;
152 
153         para->visit([&](int, const skia::textlayout::Paragraph::VisitorInfo* info) {
154             if (!info) {
155                 return;
156             }
157             canvas->drawGlyphs(info->count, info->glyphs, info->positions, info->origin,
158                                info->font, p);
159 
160             if (fFlags & kUseUnderline) {
161                 // Need to modify positions to roll-in the orign
162                 std::vector<SkPoint> pos;
163                 for (int i = 0; i < info->count; ++i) {
164                     pos.push_back({info->origin.fX + info->positions[i].fX,
165                                    info->origin.fY + info->positions[i].fY});
166                 }
167 
168                 const SkScalar X0 = pos[0].fX;
169                 const SkScalar X1 = X0 + info->advanceX;
170                 const SkScalar Y  = pos[0].fY;
171                 auto sects = info->font.getIntercepts(info->glyphs, info->count, pos.data(),
172                                                       Y+1, Y+3);
173 
174                 SkScalar x0 = X0;
175                 for (size_t i = 0; i < sects.size(); i += 2) {
176                     SkScalar x1 = sects[i] - GAP;
177                     if (x0 < x1) {
178                         canvas->drawLine(x0, Y+2, x1, Y+2, underp);
179                     }
180                     x0 = sects[i+1] + GAP;
181                 }
182                 canvas->drawLine(x0, Y+2, X1, Y+2, underp);
183             }
184 
185             if ((false)) {
186                 if (info->utf8Starts) {
187                     SkString str;
188                     for (int i = 0; i < info->count; ++i) {
189                         str.appendUnichar(gSpeech[info->utf8Starts[i]]);
190                     }
191                     SkDebugf("'%s'\n", str.c_str());
192                 }
193 
194                 // show position points
195                 for (int i = 0; i < info->count; ++i) {
196                     auto pos = info->positions[i];
197                     canvas->drawPoint(pos.fX + info->origin.fX, pos.fY + info->origin.fY, p2);
198                 }
199             }
200         });
201     }
202 
onDraw(SkCanvas * canvas,SkString * errorMsg)203     DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override {
204         if (nullptr == fPara) {
205             *errorMsg = "Font manager had no fonts or could not build paragraph.";
206             return DrawResult::kSkip;
207         }
208 
209         if (fFlags & kShowVisitor) {
210             canvas->clear(SK_ColorWHITE);
211             fPara->layout(400);
212             fPara->paint(canvas, 10, 10);
213             canvas->translate(400+10, 10);
214             this->drawFromVisitor(canvas, fPara.get());
215             return DrawResult::kOk;
216         }
217 
218         const int loop = (this->getMode() == kGM_Mode) ? 1 : 50;
219 
220         int parity = 0;
221         for (int i = 0; i < loop; ++i) {
222             SkAutoCanvasRestore acr(canvas, true);
223 
224             if (fFlags & kTimeLayout) {
225                 fPara->layout(400 + parity);
226                 parity = (parity + 1) & 1;
227             }
228             fPara->paint(canvas, 10, 10);
229         }
230         // clean up if we've been looping
231         if (loop > 1) {
232             canvas->clear(SK_ColorWHITE);
233             fPara->layout(400);
234             fPara->paint(canvas, 10, 10);
235         }
236 
237         if ((this->getMode() == kGM_Mode) && (fFlags & kTimeLayout)) {
238             return DrawResult::kSkip;
239         }
240         return DrawResult::kOk;
241     }
242 
runAsBench() const243     bool runAsBench() const override { return true; }
244 
onAnimate(double)245     bool onAnimate(double /*nanos*/) override {
246         return false;
247     }
248 
249 private:
250     using INHERITED = skiagm::GM;
251 };
252 DEF_GM(return new ParagraphGM(0);)
253 DEF_GM(return new ParagraphGM(kTimeLayout);)
254 DEF_GM(return new ParagraphGM(kUseUnderline);)
255 DEF_GM(return new ParagraphGM(kShowVisitor);)
256 DEF_GM(return new ParagraphGM(kShowVisitor | kUseUnderline);)
257