| /* | |
| * Copyright 2013 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "gm/gm.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkFont.h" | |
| #include "include/core/SkFontMetrics.h" | |
| #include "include/core/SkFontMgr.h" | |
| #include "include/core/SkFontStyle.h" | |
| #include "include/core/SkFontTypes.h" | |
| #include "include/core/SkGraphics.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkPathEffect.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkTypeface.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/effects/SkDashPathEffect.h" | |
| #include "src/core/SkFontPriv.h" | |
| #include "tools/SkMetaData.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include <array> | |
| #include <utility> | |
| // limit this just so we don't take too long to draw | |
| #define MAX_FAMILIES 30 | |
| static SkScalar drawString(SkCanvas* canvas, const SkString& text, SkScalar x, | |
| SkScalar y, const SkFont& font) { | |
| canvas->drawString(text, x, y, font, SkPaint()); | |
| return x + font.measureText(text.c_str(), text.size(), SkTextEncoding::kUTF8); | |
| } | |
| static SkScalar drawCharacter(SkCanvas* canvas, SkUnichar character, SkScalar x, | |
| SkScalar y, const SkFont& origFont, SkFontMgr* fm, | |
| const char* fontName, const char* bcp47[], int bcp47Count, | |
| const SkFontStyle& fontStyle) { | |
| SkFont font = origFont; | |
| // find typeface containing the requested character and draw it | |
| SkString ch; | |
| ch.appendUnichar(character); | |
| sk_sp<SkTypeface> typeface(fm->matchFamilyStyleCharacter(fontName, fontStyle, | |
| bcp47, bcp47Count, character)); | |
| font.setTypeface(typeface); | |
| x = drawString(canvas, ch, x, y, font) + 20; | |
| // repeat the process, but this time use the family name of the typeface | |
| // from the first pass. This emulates the behavior in Blink where it | |
| // it expects to get the same glyph when following this pattern. | |
| if (typeface) { | |
| SkString familyName; | |
| typeface->getFamilyName(&familyName); | |
| font.setTypeface(fm->legacyMakeTypeface(familyName.c_str(), typeface->fontStyle())); | |
| x = drawString(canvas, ch, x, y, font) + 20; | |
| } | |
| // find typeface containing the requested character and draw it | |
| SkFontMgr::Request request; | |
| SkFontMgr::Request::CMapEntry cmapEntry{character, 0}; | |
| request.cmapEntries = SkSpan(&cmapEntry, 1); | |
| request.bcp47 = SkSpan(bcp47, bcp47Count); | |
| request.familyName = fontName; | |
| SkFontArguments::VariationPosition::Coordinate model[4]; | |
| SkFontMgr::Request::SetModel(fontStyle, model); | |
| request.model = SkSpan(model); | |
| sk_sp<SkTypeface> typeface1 = fm->fallback(request); | |
| font.setTypeface(typeface1); | |
| x = drawString(canvas, ch, x, y, font) + 20; | |
| return x; | |
| } | |
| static const char* zh = "zh"; | |
| static const char* ja = "ja"; | |
| class FontMgrGM : public skiagm::GM { | |
| sk_sp<SkFontMgr> fFM; | |
| void onOnceBeforeDraw() override { | |
| SkGraphics::SetFontCacheLimit(16 * 1024 * 1024); | |
| fFM = ToolUtils::TestFontMgr(); | |
| } | |
| SkString getName() const override { return SkString("fontmgr_iter"); } | |
| SkISize getISize() override { return {1536, 768}; } | |
| DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { | |
| SkScalar y = 20; | |
| SkFont font = ToolUtils::DefaultFont(); | |
| font.setEdging(SkFont::Edging::kSubpixelAntiAlias); | |
| font.setSubpixel(true); | |
| font.setSize(17); | |
| SkFontMgr* fm = fFM.get(); | |
| int count = std::min(fm->countFamilies(), MAX_FAMILIES); | |
| if (count == 0) { | |
| *errorMsg = "No families in SkFontMgr"; | |
| return DrawResult::kSkip; | |
| } | |
| for (int i = 0; i < count; ++i) { | |
| SkString familyName; | |
| fm->getFamilyName(i, &familyName); | |
| font.setTypeface(ToolUtils::DefaultTypeface()); | |
| (void)drawString(canvas, familyName, 20, y, font); | |
| SkScalar x = 220; | |
| sk_sp<SkFontStyleSet> set(fm->createStyleSet(i)); | |
| for (int j = 0; j < set->count(); ++j) { | |
| SkString sname; | |
| SkFontStyle fs; | |
| set->getStyle(j, &fs, &sname); | |
| sname.appendf(" [%d %d %d]", fs.weight(), fs.width(), fs.slant()); | |
| font.setTypeface(sk_sp<SkTypeface>(set->createTypeface(j))); | |
| x = drawString(canvas, sname, x, y, font) + 20; | |
| // check to see that we get different glyphs in japanese and chinese | |
| x = drawCharacter(canvas, 0x5203, x, y, font, fm, familyName.c_str(), &zh, 1, fs); | |
| x = drawCharacter(canvas, 0x5203, x, y, font, fm, familyName.c_str(), &ja, 1, fs); | |
| // check that emoji characters are found | |
| x = drawCharacter(canvas, 0x1f601, x, y, font, fm, familyName.c_str(), nullptr,0, fs); | |
| } | |
| y += 24; | |
| } | |
| return DrawResult::kOk; | |
| } | |
| }; | |
| class FontMgrMatchGM : public skiagm::GM { | |
| sk_sp<SkFontMgr> fFM; | |
| void onOnceBeforeDraw() override { | |
| fFM = ToolUtils::TestFontMgr(); | |
| SkGraphics::SetFontCacheLimit(16 * 1024 * 1024); | |
| } | |
| SkString getName() const override { return SkString("fontmgr_match"); } | |
| SkISize getISize() override { return {640, 1024}; } | |
| void iterateFamily(SkCanvas* canvas, const SkFont& font, SkFontStyleSet* fset) { | |
| SkFont f(font); | |
| SkScalar y = 0; | |
| for (int j = 0; j < fset->count(); ++j) { | |
| SkString sname; | |
| SkFontStyle fs; | |
| fset->getStyle(j, &fs, &sname); | |
| sname.appendf(" [%d %d]", fs.weight(), fs.width()); | |
| f.setTypeface(sk_sp<SkTypeface>(fset->createTypeface(j))); | |
| SkScalar x = 0; | |
| x = drawString(canvas, sname, x, y, f) + 20; | |
| // check to see that we get different glyphs in japanese and chinese | |
| // and the style matches with no name | |
| x = drawCharacter(canvas, 0x5203, x, y, font, fFM.get(), nullptr, &zh, 1, fs); | |
| x = drawCharacter(canvas, 0x5203, x, y, font, fFM.get(), nullptr, &ja, 1, fs); | |
| y += 24; | |
| } | |
| } | |
| void exploreFamily(SkCanvas* canvas, const SkFont& font, SkFontStyleSet* fset) { | |
| SkFont f(font); | |
| SkScalar y = 0; | |
| for (int weight = 100; weight <= 900; weight += 200) { | |
| for (int width = 1; width <= 9; width += 2) { | |
| SkFontStyle fs(weight, width, SkFontStyle::kUpright_Slant); | |
| sk_sp<SkTypeface> face(fset->matchStyle(fs)); | |
| if (face) { | |
| SkString str; | |
| str.printf("request [%d %d]", fs.weight(), fs.width()); | |
| f.setTypeface(std::move(face)); | |
| SkScalar x = 0; | |
| x = drawString(canvas, str, x, y, f) + 20; | |
| // check to see that we get different glyphs in japanese and chinese | |
| // and the style matches with no name | |
| x = drawCharacter(canvas, 0x5203, x, y, font, fFM.get(), nullptr, &zh, 1, fs); | |
| x = drawCharacter(canvas, 0x5203, x, y, font, fFM.get(), nullptr, &ja, 1, fs); | |
| y += 24; | |
| } | |
| } | |
| } | |
| } | |
| DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { | |
| SkFont font = ToolUtils::DefaultFont(); | |
| font.setEdging(SkFont::Edging::kSubpixelAntiAlias); | |
| font.setSubpixel(true); | |
| font.setSize(17); | |
| static constexpr auto gNames = std::to_array<const char*>({ | |
| "Helvetica Neue", | |
| "Arial", | |
| "sans", | |
| "Roboto", | |
| }); | |
| sk_sp<SkFontStyleSet> fset; | |
| for (size_t i = 0; i < std::size(gNames); ++i) { | |
| fset = fFM->matchFamily(gNames[i]); | |
| if (fset->count() > 0) { | |
| break; | |
| } | |
| } | |
| if (!fset || fset->count() == 0) { | |
| *errorMsg = "No SkFontStyleSet"; | |
| return DrawResult::kSkip; | |
| } | |
| canvas->translate(20, 40); | |
| this->exploreFamily(canvas, font, fset.get()); | |
| canvas->translate(350, 0); | |
| this->iterateFamily(canvas, font, fset.get()); | |
| return DrawResult::kOk; | |
| } | |
| }; | |
| class FontMgrBoundsGM : public skiagm::GM { | |
| public: | |
| FontMgrBoundsGM(float scale, float skew) : fScaleX(scale) , fSkewX(skew) {} | |
| private: | |
| SkString getName() const override { | |
| if (fScaleX != 1 || fSkewX != 0) { | |
| return SkStringPrintf("fontmgr_bounds_%g_%g", fScaleX, fSkewX); | |
| } | |
| return SkString("fontmgr_bounds"); | |
| } | |
| void onOnceBeforeDraw() override { fFM = ToolUtils::TestFontMgr(); } | |
| bool onGetControls(SkMetaData* controls) override { | |
| controls->setBool("Label Bounds", fLabelBounds); | |
| return true; | |
| } | |
| void onSetControls(const SkMetaData& controls) override { | |
| controls.findBool("Label Bounds", &fLabelBounds); | |
| } | |
| static SkRect show_bounds(SkCanvas* canvas, const SkFont& font, SkScalar x, SkScalar y, | |
| SkColor boundsColor, bool labelBounds) | |
| { | |
| SkGlyphID left = 0, right = 0, top = 0, bottom = 0; | |
| SkRect min = SkRect::MakeLTRB(SK_ScalarInfinity, SK_ScalarInfinity, | |
| SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity); | |
| { | |
| int numGlyphs = font.getTypeface()->countGlyphs(); | |
| for (int i = 0; i < numGlyphs; ++i) { | |
| SkGlyphID glyphId = i; | |
| SkRect cur = font.getBounds(glyphId, nullptr); | |
| if (cur.fLeft < min.fLeft ) { min.fLeft = cur.fLeft; left = i; } | |
| if (cur.fTop < min.fTop ) { min.fTop = cur.fTop ; top = i; } | |
| if (min.fRight < cur.fRight ) { min.fRight = cur.fRight; right = i; } | |
| if (min.fBottom < cur.fBottom) { min.fBottom = cur.fBottom; bottom = i; } | |
| } | |
| } | |
| SkRect fontBounds = SkFontPriv::GetFontBounds(font); | |
| SkRect drawBounds = min; | |
| drawBounds.join(fontBounds); | |
| SkAutoCanvasRestore acr(canvas, true); | |
| canvas->translate(x - drawBounds.left(), y); | |
| SkPaint boundsPaint; | |
| boundsPaint.setAntiAlias(true); | |
| boundsPaint.setColor(boundsColor); | |
| boundsPaint.setStyle(SkPaint::kStroke_Style); | |
| canvas->drawRect(fontBounds, boundsPaint); | |
| const SkScalar intervals[] = { 10.f, 10.f }; | |
| boundsPaint.setPathEffect(SkDashPathEffect::Make(intervals, 0.f)); | |
| canvas->drawRect(min, boundsPaint); | |
| SkFontMetrics fm; | |
| font.getMetrics(&fm); | |
| SkPaint metricsPaint(boundsPaint); | |
| metricsPaint.setStyle(SkPaint::kFill_Style); | |
| metricsPaint.setAlphaf(0.25f); | |
| if ((fm.fFlags & SkFontMetrics::kUnderlinePositionIsValid_Flag) && | |
| (fm.fFlags & SkFontMetrics::kUnderlineThicknessIsValid_Flag)) | |
| { | |
| SkRect underline{ min.fLeft, fm.fUnderlinePosition, | |
| min.fRight, fm.fUnderlinePosition + fm.fUnderlineThickness }; | |
| canvas->drawRect(underline, metricsPaint); | |
| } | |
| if ((fm.fFlags & SkFontMetrics::kStrikeoutPositionIsValid_Flag) && | |
| (fm.fFlags & SkFontMetrics::kStrikeoutThicknessIsValid_Flag)) | |
| { | |
| SkRect strikeout{ min.fLeft, fm.fStrikeoutPosition - fm.fStrikeoutThickness, | |
| min.fRight, fm.fStrikeoutPosition }; | |
| canvas->drawRect(strikeout, metricsPaint); | |
| } | |
| struct GlyphToDraw { | |
| SkGlyphID id; | |
| SkPoint location; | |
| SkScalar rotation; | |
| } glyphsToDraw [] = { | |
| {left, {min.left(), min.centerY()}, 270}, | |
| {right, {min.right(), min.centerY()}, 90}, | |
| {top, {min.centerX(), min.top() }, 0}, | |
| {bottom, {min.centerX(), min.bottom() }, 180}, | |
| }; | |
| SkFont labelFont; | |
| labelFont.setEdging(SkFont::Edging::kAntiAlias); | |
| labelFont.setTypeface(ToolUtils::DefaultPortableTypeface()); | |
| if (labelBounds) { | |
| SkString name; | |
| font.getTypeface()->getFamilyName(&name); | |
| canvas->drawString(name, min.fLeft, min.fBottom, labelFont, SkPaint()); | |
| } | |
| for (const GlyphToDraw& glyphToDraw : glyphsToDraw) { | |
| SkPath path = font.getPath(glyphToDraw.id).value_or(SkPath()); | |
| SkPaint::Style style = path.isEmpty() ? SkPaint::kFill_Style : SkPaint::kStroke_Style; | |
| SkPaint glyphPaint; | |
| glyphPaint.setStyle(style); | |
| canvas->drawSimpleText(&glyphToDraw.id, sizeof(glyphToDraw.id), | |
| SkTextEncoding::kGlyphID, 0, 0, font, glyphPaint); | |
| if (labelBounds) { | |
| SkAutoCanvasRestore acr2(canvas, true); | |
| canvas->translate(glyphToDraw.location.fX, glyphToDraw.location.fY); | |
| canvas->rotate(glyphToDraw.rotation); | |
| SkString glyphStr; | |
| glyphStr.appendS32(glyphToDraw.id); | |
| canvas->drawString(glyphStr, 0, 0, labelFont, SkPaint()); | |
| } | |
| } | |
| return drawBounds; | |
| } | |
| SkISize getISize() override { return {1024, 850}; } | |
| DrawResult onDraw(SkCanvas* canvas, SkString* errorMsg) override { | |
| SkFont font = ToolUtils::DefaultFont(); | |
| font.setEdging(SkFont::Edging::kAntiAlias); | |
| font.setSubpixel(true); | |
| font.setSize(100); | |
| font.setScaleX(fScaleX); | |
| font.setSkewX(fSkewX); | |
| const std::array<SkColor, 2> boundsColors = {SK_ColorRED, SK_ColorBLUE}; | |
| SkFontMgr* fm = fFM.get(); | |
| int count = fm->countFamilies(); | |
| if (count == 0) { | |
| *errorMsg = "No families in SkFontMgr under test."; | |
| return DrawResult::kSkip; | |
| } | |
| int index = 0; | |
| SkScalar x = 0, y = 0; | |
| canvas->translate(10, 120); | |
| int typefacesVisited = 0; | |
| for (int i = 0; i < count && typefacesVisited < 32; ++i) { | |
| sk_sp<SkFontStyleSet> set(fm->createStyleSet(i)); | |
| int stylesVisited = 0; | |
| for (int j = 0; j < set->count() && typefacesVisited < 32 && stylesVisited < 3; ++j) { | |
| font.setTypeface(sk_sp<SkTypeface>(set->createTypeface(j))); | |
| // Fonts with lots of glyphs are interesting, but can take a long time to find | |
| // the glyphs which make up the maximum extent. | |
| SkTypeface* typeface = font.getTypeface(); | |
| if (typeface && 0 < typeface->countGlyphs() && typeface->countGlyphs() < 1000) { | |
| ++typefacesVisited; | |
| ++stylesVisited; | |
| SkColor color = boundsColors[index & 1]; | |
| SkRect drawBounds = show_bounds(canvas, font, x, y, color, fLabelBounds); | |
| x += drawBounds.width() + 20; | |
| index += 1; | |
| if (x > 900) { | |
| x = 0; | |
| y += 160; | |
| } | |
| if (y >= 700) { | |
| return DrawResult::kOk; | |
| } | |
| } | |
| } | |
| } | |
| return DrawResult::kOk; | |
| } | |
| sk_sp<SkFontMgr> fFM; | |
| const SkScalar fScaleX; | |
| const SkScalar fSkewX; | |
| bool fLabelBounds = false; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////// | |
| DEF_GM(return new FontMgrGM;) | |
| DEF_GM(return new FontMgrMatchGM;) | |
| DEF_GM(return new FontMgrBoundsGM(1, 0);) | |
| DEF_GM(return new FontMgrBoundsGM(0.75f, 0);) | |
| DEF_GM(return new FontMgrBoundsGM(1, -0.25f);) |