| /* | |
| * Copyright 2024 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkContourMeasure.h" | |
| #include "include/core/SkFont.h" | |
| #include "include/core/SkFontMetrics.h" | |
| #include "include/core/SkFontMgr.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathBuilder.h" | |
| #include "include/core/SkRRect.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkTypeface.h" | |
| #include "include/utils/SkNoDrawCanvas.h" | |
| #include "src/core/SkRandom.h" | |
| #include "tools/SkMetaData.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include "tools/viewer/Slide.h" | |
| namespace { | |
| class TypefaceSlide : public Slide { | |
| public: | |
| TypefaceSlide() { fName = "Typeface Viewer"; } | |
| void load(SkScalar w, SkScalar h) override { | |
| fWindowSize = {w, h}; | |
| fBaseTypeface = ToolUtils::CreateTypefaceFromResource("fonts/Variable.ttf"); | |
| fCurrentTypeface = fBaseTypeface; | |
| fVariationSliders = ToolUtils::VariationSliders(fCurrentTypeface.get(), fVariationPosition); | |
| fPathDirectionIndicator = SkPathBuilder() | |
| .moveTo(0, -3) | |
| .lineTo(3, 0) | |
| .lineTo(0, 3) | |
| .close() | |
| .detach(); | |
| fPathDirectionIndicatorPaint.setColor(SK_ColorRED); | |
| fPathDirectionIndicatorPaint.setStroke(true); | |
| fPathDirectionIndicatorPaint.setStrokeWidth(0); | |
| } | |
| void resize(SkScalar w, SkScalar h) override { fWindowSize = {w, h}; } | |
| void unload() override { | |
| fBaseTypeface = nullptr; | |
| fCurrentTypeface = nullptr; | |
| } | |
| SkISize getDimensions() const override { | |
| if (fDrawArea.isEmpty()) { | |
| TypefaceSlide& self = *const_cast<TypefaceSlide*>(this); | |
| SkNoDrawCanvas noDraw(fWindowSize.width(), fWindowSize.height()); | |
| self.draw(&noDraw); | |
| } | |
| return fDrawArea; | |
| } | |
| void updateCurrentTypeface() { | |
| if (!fBaseTypeface) { | |
| return; | |
| } | |
| SkSpan<const SkFontArguments::VariationPosition::Coordinate> coords = | |
| fVariationSliders.getCoordinates(); | |
| SkFontArguments::VariationPosition varPos = {coords.data(), | |
| static_cast<int>(coords.size())}; | |
| SkFontArguments args; | |
| args.setVariationDesignPosition(varPos); | |
| fCurrentTypeface = fBaseTypeface->makeClone(args); | |
| fCurrentTypefaceDirty = false; | |
| } | |
| void draw(SkCanvas* canvas) override { | |
| SkPaint notationPaint; | |
| notationPaint.setColor(SK_ColorRED); | |
| SkFont notationFont(ToolUtils::DefaultTypeface(), 8.0); | |
| SkPaint paint; | |
| if (fOutline) { | |
| paint.setStroke(true); | |
| paint.setStrokeWidth(0); | |
| } | |
| if (fCurrentTypefaceDirty) { | |
| this->updateCurrentTypeface(); | |
| } | |
| SkFont font(fCurrentTypeface, fFontSize); | |
| SkFontMetrics metrics; | |
| font.getMetrics(&metrics); | |
| SkPoint origin{0, 0}; | |
| SkPoint position{0, 0}; | |
| SkRect drawBounds = SkRect::MakeXYWH(0, 0, 1, 1); | |
| struct Line { | |
| SkRect bounds; | |
| SkGlyphID firstGlyph; // inclusive | |
| SkGlyphID lastGlyph; // inclusive | |
| int number; | |
| } line{{}, fCurrentGlyphID, fCurrentGlyphID, 0}; | |
| int numGlyphs = fCurrentTypeface->countGlyphs(); | |
| if (numGlyphs == 0) { | |
| fDrawArea.setEmpty(); | |
| return; | |
| } | |
| SkGlyphID lastGlyph = numGlyphs - 1; | |
| for (SkGlyphID glyph = line.firstGlyph; line.number < 2; ++glyph) { | |
| // measure a line | |
| SkRect beginLineGlyphBounds; | |
| { | |
| SkRect newLineBounds = line.bounds; | |
| if (glyph != line.firstGlyph) { | |
| newLineBounds.fRight += 10; | |
| } | |
| SkScalar advance; | |
| SkRect glyphBounds; | |
| font.getWidthsBounds({&glyph, 1}, {&advance, 1}, {&glyphBounds, 1}, nullptr); | |
| SkRect advanceBounds = SkRect::MakeWH(advance, 1); | |
| SkRect glyphAndAdvanceBounds = glyphBounds; | |
| glyphAndAdvanceBounds.join(advanceBounds); | |
| beginLineGlyphBounds = glyphAndAdvanceBounds; | |
| beginLineGlyphBounds.offset(-beginLineGlyphBounds.fLeft, 0); | |
| SkRect glyphDrawBounds = beginLineGlyphBounds.makeOffset(newLineBounds.right(), 0); | |
| if (line.number == -1) { | |
| SkPaint p; | |
| p.setColor(SK_ColorBLUE); | |
| canvas->drawRect(glyphDrawBounds, p); | |
| } | |
| newLineBounds.join(glyphDrawBounds); | |
| if (newLineBounds.width() < fWindowSize.width()) { | |
| line.lastGlyph = glyph; | |
| line.bounds = newLineBounds; | |
| if (glyph != lastGlyph) { | |
| continue; | |
| } | |
| } | |
| } | |
| // draw the line | |
| position.fY -= line.bounds.top(); | |
| for (SkGlyphID gid = line.firstGlyph; gid <= line.lastGlyph; ++gid) { | |
| if (gid != line.firstGlyph) { | |
| position.fX += 10; | |
| } | |
| SkScalar advance; | |
| SkRect glyphBounds; | |
| font.getWidthsBounds({&gid, 1}, {&advance, 1}, {&glyphBounds, 1}, nullptr); | |
| SkRect advanceBounds = SkRect::MakeWH(advance, 1); | |
| SkRect glyphAndAdvanceBounds = glyphBounds; | |
| glyphAndAdvanceBounds.join(advanceBounds); | |
| position.fX -= glyphAndAdvanceBounds.left(); | |
| if (fDrawGlyphMetrics) { | |
| SkPaint metricPaint; | |
| metricPaint.setColor(SK_ColorRED); | |
| canvas->drawRect(glyphBounds.makeOffset(position), metricPaint); | |
| metricPaint.setColor(SK_ColorGREEN); | |
| canvas->drawRect(advanceBounds.makeOffset(position), metricPaint); | |
| } | |
| canvas->drawGlyphs({&gid, 1}, {&position, 1}, origin, font, paint); | |
| if (fGlyphNumbers) { | |
| SkString gidStr; | |
| gidStr.appendS32(gid); | |
| canvas->drawSimpleText(gidStr.c_str(), gidStr.size(), SkTextEncoding::kUTF8, | |
| position.x(), position.y(), notationFont, notationPaint); | |
| } | |
| // TODO: also handle drawable by using a paint override canvas? | |
| if (fOutline) { | |
| SkContourMeasureIter iter(font.getPath(gid).value_or(SkPath()), false); | |
| sk_sp<SkContourMeasure> contour; | |
| int contourIndex = 0; | |
| while ((contour = iter.next())) { | |
| SkPoint contourStart; | |
| SkVector tangent; | |
| if (contour->getPosTan(0, &contourStart, &tangent)) { | |
| contourStart += position; | |
| SkAutoCanvasRestore acr(canvas, true); | |
| SkMatrix matrix; | |
| matrix.setSinCos(tangent.y(), tangent.x(), 0, 0); | |
| matrix.postTranslate(contourStart.x(), contourStart.y()); | |
| canvas->concat(matrix); | |
| if (fOutlineContourNumbers) { | |
| SkString contourStr; | |
| contourStr.appendS32(contourIndex); | |
| canvas->drawSimpleText(contourStr.c_str(), contourStr.size(), | |
| SkTextEncoding::kUTF8, 0, 0, | |
| notationFont, notationPaint); | |
| } else { | |
| canvas->drawPath(fPathDirectionIndicator, fPathDirectionIndicatorPaint); | |
| } | |
| ++contourIndex; | |
| } | |
| } | |
| } | |
| position.fX += glyphAndAdvanceBounds.right(); | |
| } | |
| if (line.lastGlyph == lastGlyph) { | |
| break; | |
| } | |
| drawBounds.join(line.bounds.makeOffset(-line.bounds.fLeft, position.fY)); | |
| position.fX = 0; | |
| position.fY += line.bounds.bottom() + 10; | |
| line.bounds = beginLineGlyphBounds; | |
| line.firstGlyph = glyph; | |
| line.lastGlyph = glyph; | |
| ++line.number; | |
| } | |
| fDrawArea = drawBounds.roundOut().size(); | |
| } | |
| bool onGetControls(SkMetaData* controls) override { | |
| // requested font size | |
| SkScalar size[3] = {fFontSize, 0, 256}; | |
| controls->setScalars("Size", 3, size); | |
| // the first glyph on the first line | |
| SkScalar glyph[3] = {SkScalar(fCurrentGlyphID), 0, SkScalar(this->fCurrentTypeface->countGlyphs())}; | |
| controls->setScalars("Glyph", 3, glyph); | |
| // TODO: limit number of glyphs | |
| // TODO: choose typeface factory | |
| // TODO: choose between typefaces | |
| // TODO: font metrics like underline, strikeout, x-height, cap-height, etc. | |
| // show glyph metrics like advances and bounds | |
| controls->setBool("Glyph Metrics", fDrawGlyphMetrics); | |
| // Show glyph numbers at origin | |
| controls->setBool("Glyph numbers", fGlyphNumbers); | |
| // hairline contours with initial direction mark | |
| controls->setBool("Outline", fOutline); | |
| // draw contour numbers instead of initial contour direction mark | |
| controls->setBool("Outline contour numbers", fOutlineContourNumbers); | |
| return fVariationSliders.writeControls(controls); | |
| } | |
| void onSetControls(const SkMetaData& controls) override { | |
| SkScalar size[3] = {0}; | |
| int numReturnedScalars = 0; | |
| SkASSERT_RELEASE(controls.findScalars("Size", &numReturnedScalars, size)); | |
| SkASSERT_RELEASE(numReturnedScalars == 3); | |
| if (fFontSize != size[0]) { | |
| fFontSize = size[0]; | |
| fDrawArea.setEmpty(); | |
| } | |
| SkScalar glyph[3] = {0}; | |
| numReturnedScalars = 0; | |
| SkASSERT_RELEASE(controls.findScalars("Glyph", &numReturnedScalars, glyph)); | |
| SkASSERT_RELEASE(numReturnedScalars == 3); | |
| if (fCurrentGlyphID != SkScalarRoundToInt(glyph[0])) { | |
| fCurrentGlyphID = SkScalarRoundToInt(glyph[0]); | |
| fDrawArea.setEmpty(); | |
| } | |
| controls.findBool("Glyph Metrics", &fDrawGlyphMetrics); | |
| controls.findBool("Glyph numbers", &fGlyphNumbers); | |
| controls.findBool("Outline", &fOutline); | |
| controls.findBool("Outline contour numbers", &fOutlineContourNumbers); | |
| fVariationSliders.readControls(controls, &fCurrentTypefaceDirty); | |
| } | |
| private: | |
| sk_sp<SkTypeface> fBaseTypeface; | |
| sk_sp<SkTypeface> fCurrentTypeface; | |
| std::unique_ptr<SkFontArguments::VariationPosition::Coordinate[]> fCoordinates; | |
| SkFontArguments::VariationPosition fVariationPosition; | |
| ToolUtils::VariationSliders fVariationSliders; | |
| bool fCurrentTypefaceDirty = true; | |
| SkScalar fFontSize = 80; | |
| bool fOutline = false; | |
| bool fOutlineContourNumbers = false; | |
| bool fGlyphNumbers = false; | |
| bool fDrawGlyphMetrics = false; | |
| SkGlyphID fCurrentGlyphID = 0; | |
| SkSize fWindowSize; | |
| SkISize fDrawArea; | |
| SkPath fPathDirectionIndicator; | |
| SkPaint fPathDirectionIndicatorPaint; | |
| }; | |
| } //namespace | |
| ////////////////////////////////////////////////////////////////////////////// | |
| DEF_SLIDE( return new TypefaceSlide(); ) |