xref: /aosp_15_r20/external/skia/tools/viewer/TypefaceSlide.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2024 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 #include "include/core/SkCanvas.h"
8 #include "include/core/SkContourMeasure.h"
9 #include "include/core/SkFont.h"
10 #include "include/core/SkFontMetrics.h"
11 #include "include/core/SkFontMgr.h"
12 #include "include/core/SkPath.h"
13 #include "include/core/SkRRect.h"
14 #include "include/core/SkTypeface.h"
15 #include "include/utils/SkNoDrawCanvas.h"
16 #include "src/base/SkRandom.h"
17 #include "tools/SkMetaData.h"
18 #include "tools/ToolUtils.h"
19 #include "tools/fonts/FontToolUtils.h"
20 #include "tools/timer/TimeUtils.h"
21 #include "tools/viewer/Slide.h"
22 
23 namespace {
24 
25 class TypefaceSlide : public Slide {
26 public:
TypefaceSlide()27     TypefaceSlide() { fName = "Typeface Viewer"; }
28 
load(SkScalar w,SkScalar h)29     void load(SkScalar w, SkScalar h) override {
30         fWindowSize = {w, h};
31 
32         fBaseTypeface = ToolUtils::CreateTypefaceFromResource("fonts/Variable.ttf");
33         fCurrentTypeface = fBaseTypeface;
34         fVariationSliders = ToolUtils::VariationSliders(fCurrentTypeface.get(), fVariationPosition);
35 
36         fPathDirectionIndicator.reset();
37         fPathDirectionIndicator.moveTo(0, -3);
38         fPathDirectionIndicator.lineTo(3, 0);
39         fPathDirectionIndicator.lineTo(0, 3);
40         fPathDirectionIndicator.close();
41 
42         fPathDirectionIndicatorPaint.setColor(SK_ColorRED);
43         fPathDirectionIndicatorPaint.setStroke(true);
44         fPathDirectionIndicatorPaint.setStrokeWidth(0);
45     }
46 
resize(SkScalar w,SkScalar h)47     void resize(SkScalar w, SkScalar h) override { fWindowSize = {w, h}; }
48 
unload()49     void unload() override {
50         fBaseTypeface = nullptr;
51         fCurrentTypeface = nullptr;
52     }
53 
getDimensions() const54     SkISize getDimensions() const override {
55         if (fDrawArea.isEmpty()) {
56             TypefaceSlide& self = *const_cast<TypefaceSlide*>(this);
57             SkNoDrawCanvas noDraw(fWindowSize.width(), fWindowSize.height());
58             self.draw(&noDraw);
59         }
60         return fDrawArea;
61     }
62 
updateCurrentTypeface()63     void updateCurrentTypeface() {
64         if (!fBaseTypeface) {
65             return;
66         }
67         SkSpan<const SkFontArguments::VariationPosition::Coordinate> coords =
68                 fVariationSliders.getCoordinates();
69         SkFontArguments::VariationPosition varPos = {coords.data(),
70                                                      static_cast<int>(coords.size())};
71         SkFontArguments args;
72         args.setVariationDesignPosition(varPos);
73         fCurrentTypeface = fBaseTypeface->makeClone(args);
74         fCurrentTypefaceDirty = false;
75     }
76 
draw(SkCanvas * canvas)77     void draw(SkCanvas* canvas) override {
78         SkPaint paint;
79         if (fOutline) {
80             paint.setStroke(true);
81             paint.setStrokeWidth(0);
82         }
83 
84         if (fCurrentTypefaceDirty) {
85             this->updateCurrentTypeface();
86         }
87         SkFont font(fCurrentTypeface, fFontSize);
88         SkFontMetrics metrics;
89         font.getMetrics(&metrics);
90 
91         SkPoint origin{0, 0};
92         SkPoint position{0, 0};
93         SkRect drawBounds = SkRect::MakeXYWH(0, 0, 1, 1);
94         struct Line {
95             SkRect bounds;
96             SkGlyphID firstGlyph = 0; // inclusive
97             SkGlyphID lastGlyph = 0; // inclusive
98             int number = 0;
99         } line;
100 
101         int numGlyphs = fCurrentTypeface->countGlyphs();
102         if (numGlyphs == 0) {
103             fDrawArea.setEmpty();
104             return;
105         }
106         SkGlyphID lastGlyph = numGlyphs - 1;
107         for (SkGlyphID glyph = 0; true; ++glyph) {
108             // measure a line
109             SkRect beginLineGlyphBounds;
110             {
111                 SkRect newLineBounds = line.bounds;
112                 if (glyph != line.firstGlyph) {
113                     newLineBounds.fRight += 10;
114                 }
115                 SkScalar advance;
116                 SkRect glyphBounds;
117                 font.getWidths(&glyph, 1, &advance, &glyphBounds);
118                 SkRect advanceBounds = SkRect::MakeWH(advance, 1);
119                 SkRect glyphAndAdvanceBounds = glyphBounds;
120                 glyphAndAdvanceBounds.join(advanceBounds);
121                 beginLineGlyphBounds = glyphAndAdvanceBounds;
122                 beginLineGlyphBounds.offset(-beginLineGlyphBounds.fLeft, 0);
123 
124                 SkRect glyphDrawBounds = beginLineGlyphBounds.makeOffset(newLineBounds.right(), 0);
125                 if (line.number == -1) {
126                     SkPaint p;
127                     p.setColor(SK_ColorBLUE);
128                     canvas->drawRect(glyphDrawBounds, p);
129                 }
130                 newLineBounds.join(glyphDrawBounds);
131                 if (newLineBounds.width() < fWindowSize.width()) {
132                     line.lastGlyph = glyph;
133                     line.bounds = newLineBounds;
134                     if (glyph != lastGlyph) {
135                         continue;
136                     }
137                 }
138             }
139 
140             // draw the line
141             position.fY -= line.bounds.top();
142             for (SkGlyphID gid = line.firstGlyph; gid <= line.lastGlyph; ++gid) {
143                 if (gid != line.firstGlyph) {
144                     position.fX += 10;
145                 }
146                 SkScalar advance;
147                 SkRect glyphBounds;
148                 font.getWidths(&gid, 1, &advance, &glyphBounds);
149                 SkRect advanceBounds = SkRect::MakeWH(advance, 1);
150                 SkRect glyphAndAdvanceBounds = glyphBounds;
151                 glyphAndAdvanceBounds.join(advanceBounds);
152 
153                 position.fX -= glyphAndAdvanceBounds.left();
154 
155                 if (fDrawGlyphMetrics) {
156                     SkPaint metricPaint;
157                     metricPaint.setColor(SK_ColorRED);
158                     canvas->drawRect(glyphBounds.makeOffset(position), metricPaint);
159                     metricPaint.setColor(SK_ColorGREEN);
160                     canvas->drawRect(advanceBounds.makeOffset(position), metricPaint);
161                 }
162 
163                 canvas->drawGlyphs(1, &gid, &position, origin, font, paint);
164 
165                 // TODO: also handle drawable by using a paint override canvas?
166                 SkPath glyphPath;
167                 if (fOutline && font.getPath(gid, &glyphPath)) {
168                     SkContourMeasureIter iter(glyphPath, false);
169                     sk_sp<SkContourMeasure> contour;
170                     while ((contour = iter.next())) {
171                         SkPoint contourStart;
172                         SkVector tangent;
173                         if (contour->getPosTan(0, &contourStart, &tangent)) {
174                             contourStart += position;
175                             SkAutoCanvasRestore acr(canvas, true);
176                             SkMatrix matrix;
177                             matrix.setSinCos(tangent.y(), tangent.x(), 0, 0);
178                             matrix.postTranslate(contourStart.x(), contourStart.y());
179                             canvas->concat(matrix);
180                             canvas->drawPath(fPathDirectionIndicator, fPathDirectionIndicatorPaint);
181                         }
182                     }
183                 }
184 
185                 position.fX += glyphAndAdvanceBounds.right();
186             }
187             if (line.lastGlyph == lastGlyph) {
188                 break;
189             }
190             drawBounds.join(line.bounds.makeOffset(-line.bounds.fLeft, position.fY));
191             position.fX = 0;
192             position.fY += line.bounds.bottom() + 10;
193             line.bounds = beginLineGlyphBounds;
194             line.firstGlyph = glyph;
195             line.lastGlyph = glyph;
196             ++line.number;
197         }
198 
199         fDrawArea = drawBounds.roundOut().size();
200     }
201 
onGetControls(SkMetaData * controls)202     bool onGetControls(SkMetaData* controls) override {
203         // requested font size
204         SkScalar size[3] = {fFontSize, 0, 256};
205         controls->setScalars("Size", 3, size);
206 
207         // TODO: toggle glyph numbers on top?
208         // TODO: limit number of glyphs and set first glyph
209         // TODO: choose typeface factory
210         // TODO: choose between typefaces
211         // TODO: font metrics like underline, strikeout, x-height, cap-height, etc.
212 
213         // show glyph metrics like advances and bounds
214         controls->setBool("Glyph Metrics", fDrawGlyphMetrics);
215 
216         // hairline contours with initial direction mark
217         controls->setBool("Outline", fOutline);
218 
219         return fVariationSliders.writeControls(controls);
220     }
221 
onSetControls(const SkMetaData & controls)222     void onSetControls(const SkMetaData& controls) override {
223         SkScalar size[3] = {0};
224         int numReturnedScalars = 0;
225         SkASSERT_RELEASE(controls.findScalars("Size", &numReturnedScalars, size));
226         SkASSERT_RELEASE(numReturnedScalars == 3);
227         if (fFontSize != size[0]) {
228             fFontSize = size[0];
229             fDrawArea.setEmpty();
230         }
231 
232         controls.findBool("Glyph Metrics", &fDrawGlyphMetrics);
233         controls.findBool("Outline", &fOutline);
234 
235         fVariationSliders.readControls(controls, &fCurrentTypefaceDirty);
236     }
237 
238 private:
239     sk_sp<SkTypeface> fBaseTypeface;
240     sk_sp<SkTypeface> fCurrentTypeface;
241 
242     std::unique_ptr<SkFontArguments::VariationPosition::Coordinate[]> fCoordinates;
243     SkFontArguments::VariationPosition fVariationPosition;
244     ToolUtils::VariationSliders fVariationSliders;
245     bool fCurrentTypefaceDirty = true;
246     SkScalar fFontSize = 12;
247     bool fOutline = false;
248     bool fDrawGlyphMetrics = false;
249 
250     SkSize fWindowSize;
251     SkISize fDrawArea;
252 
253     SkPath fPathDirectionIndicator;
254     SkPaint fPathDirectionIndicatorPaint;
255 };
256 
257 }  //namespace
258 
259 //////////////////////////////////////////////////////////////////////////////
260 
261 DEF_SLIDE( return new TypefaceSlide(); )
262