skia2/gm/typeface.cpp

454 lines
15 KiB
C++
Raw Normal View History

/*
* Copyright 2012 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/SkBlurTypes.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontStyle.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTypeface.h"
#include "include/core/SkTypes.h"
#include "include/private/SkTemplates.h"
#include "tools/Resources.h"
#include <string.h>
#include <utility>
static void getGlyphPositions(const SkFont& font, const uint16_t glyphs[],
int count, SkScalar x, SkScalar y, SkPoint pos[]) {
SkAutoSTMalloc<128, SkScalar> widthStorage(count);
SkScalar* widths = widthStorage.get();
font.getWidths(glyphs, count, widths);
for (int i = 0; i < count; ++i) {
pos[i].set(x, y);
x += widths[i];
}
}
static void applyKerning(SkPoint pos[], const int32_t adjustments[], int count,
const SkFont& font) {
SkScalar scale = font.getSize() / font.getTypefaceOrDefault()->getUnitsPerEm();
SkScalar globalAdj = 0;
for (int i = 0; i < count - 1; ++i) {
globalAdj += adjustments[i] * scale;
pos[i + 1].fX += globalAdj;
}
}
static void drawKernText(SkCanvas* canvas, const void* text, size_t len,
SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint) {
SkTypeface* face = font.getTypefaceOrDefault();
if (!face) {
canvas->drawSimpleText(text, len, SkTextEncoding::kUTF8, x, y, font, paint);
return;
}
SkAutoSTMalloc<128, uint16_t> glyphStorage(len);
uint16_t* glyphs = glyphStorage.get();
int glyphCount = font.textToGlyphs(text, len, SkTextEncoding::kUTF8, glyphs, len);
if (glyphCount < 1) {
return;
}
SkAutoSTMalloc<128, int32_t> adjustmentStorage(glyphCount - 1);
int32_t* adjustments = adjustmentStorage.get();
if (!face->getKerningPairAdjustments(glyphs, glyphCount, adjustments)) {
canvas->drawSimpleText(text, len, SkTextEncoding::kUTF8, x, y, font, paint);
return;
}
SkTextBlobBuilder builder;
auto rec = builder.allocRunPos(font, glyphCount);
memcpy(rec.glyphs, glyphs, glyphCount * sizeof(SkGlyphID));
getGlyphPositions(font, glyphs, glyphCount, x, y, rec.points());
applyKerning(rec.points(), adjustments, glyphCount, font);
canvas->drawTextBlob(builder.make(), 0, 0, paint);
}
static constexpr SkFontStyle gStyles[] = {
SkFontStyle::Normal(),
SkFontStyle::Bold(),
SkFontStyle::Italic(),
SkFontStyle::BoldItalic(),
};
constexpr int gStylesCount = SK_ARRAY_COUNT(gStyles);
class TypefaceStylesGM : public skiagm::GM {
sk_sp<SkTypeface> fFaces[gStylesCount];
bool fApplyKerning;
public:
TypefaceStylesGM(bool applyKerning) : fApplyKerning(applyKerning) {}
protected:
Move font loading in gm tests and benches out of constructors Constructing the gm tests and benches causes many calls to font loads. This is visible as profiling samples in fontconfig and freetype on Linux for all profiling runs of nanobench. This complicates analysis of test-cases that are suspected of being slow due to font-related issues. Move the font loading to GM::onOnceBeforeDraw and Benchmark::onPreDraw. This way the code is not executed if the testcase does not match the nanobench --match filter. This way the samples in font-related code are more easy to identify as legitimate occurances caused by the testcase. This should not cause differences in timings, because: * Benchmark::preDraw / onPreDraw is defined to be run outside the timer * GM::runAsBench is not enabled for any of the modified testcases. Also nanobench untimed warmup round should run the onOnceBeforeDraw. (and there are other GM::runAsBench gms already doing loading in onOnceBeforeDraw). Changes the behavior: In TextBench: Before, the test would report two different gms with the same name if the color emoji font was not loaded successfully. After, the test always reports all tests as individual names. Generally: The errors from loading fonts now print inbetween each testcase, as opposed to printing during construction phase. Sample output: ( 143/145 MB 1872) 14.7ms 8888 gm quadclosepathResource /fonts/Funkster.ttf not a valid font. ( 160/160 MB 1831) 575µs 8888 gm surfacenewResource /fonts/Funkster.ttf not a valid font. ( 163/165 MB 1816) 12.5ms 8888 gm linepathResource /fonts/Funkster.ttf not a valid font. ( 263/411 MB 1493) 118ms 8888 gm typefacestyles_kerningResource /fonts/Funkster.ttf not a valid font. ( 374/411 MB 1231) 7.16ms 565 gm getpostextpathResource /fonts/Funkster.ttf not a valid font. ( 323/411 MB 1179) 4.92ms 565 gm stringartResource /fonts/Funkster.ttf not a valid font. ( 347/493 MB 917) 191ms 565 gm patch_gridResource /fonts/Funkster.ttf not a valid font. ( 375/493 MB 857) 23.9ms gpu gm clipdrawdrawCannot render path (0) ( 393/493 MB 706) 2.91ms unit test ParsePath------ png error IEND: CRC error ( 394/493 MB 584) 166ms gpu gm hairmodesResource /fonts/Funkster.ttf not a valid font. Resource /fonts/Funkster.ttf not a valid font. Resource /fonts/Funkster.ttf not a valid font. ... Review URL: https://codereview.chromium.org/1144023002
2015-05-21 13:15:28 +00:00
void onOnceBeforeDraw() override {
for (int i = 0; i < gStylesCount; i++) {
fFaces[i] = SkTypeface::MakeFromName(nullptr, gStyles[i]);
Move font loading in gm tests and benches out of constructors Constructing the gm tests and benches causes many calls to font loads. This is visible as profiling samples in fontconfig and freetype on Linux for all profiling runs of nanobench. This complicates analysis of test-cases that are suspected of being slow due to font-related issues. Move the font loading to GM::onOnceBeforeDraw and Benchmark::onPreDraw. This way the code is not executed if the testcase does not match the nanobench --match filter. This way the samples in font-related code are more easy to identify as legitimate occurances caused by the testcase. This should not cause differences in timings, because: * Benchmark::preDraw / onPreDraw is defined to be run outside the timer * GM::runAsBench is not enabled for any of the modified testcases. Also nanobench untimed warmup round should run the onOnceBeforeDraw. (and there are other GM::runAsBench gms already doing loading in onOnceBeforeDraw). Changes the behavior: In TextBench: Before, the test would report two different gms with the same name if the color emoji font was not loaded successfully. After, the test always reports all tests as individual names. Generally: The errors from loading fonts now print inbetween each testcase, as opposed to printing during construction phase. Sample output: ( 143/145 MB 1872) 14.7ms 8888 gm quadclosepathResource /fonts/Funkster.ttf not a valid font. ( 160/160 MB 1831) 575µs 8888 gm surfacenewResource /fonts/Funkster.ttf not a valid font. ( 163/165 MB 1816) 12.5ms 8888 gm linepathResource /fonts/Funkster.ttf not a valid font. ( 263/411 MB 1493) 118ms 8888 gm typefacestyles_kerningResource /fonts/Funkster.ttf not a valid font. ( 374/411 MB 1231) 7.16ms 565 gm getpostextpathResource /fonts/Funkster.ttf not a valid font. ( 323/411 MB 1179) 4.92ms 565 gm stringartResource /fonts/Funkster.ttf not a valid font. ( 347/493 MB 917) 191ms 565 gm patch_gridResource /fonts/Funkster.ttf not a valid font. ( 375/493 MB 857) 23.9ms gpu gm clipdrawdrawCannot render path (0) ( 393/493 MB 706) 2.91ms unit test ParsePath------ png error IEND: CRC error ( 394/493 MB 584) 166ms gpu gm hairmodesResource /fonts/Funkster.ttf not a valid font. Resource /fonts/Funkster.ttf not a valid font. Resource /fonts/Funkster.ttf not a valid font. ... Review URL: https://codereview.chromium.org/1144023002
2015-05-21 13:15:28 +00:00
}
}
SkString onShortName() override {
SkString name("typefacestyles");
if (fApplyKerning) {
name.append("_kerning");
}
return name;
}
SkISize onISize() override {
return SkISize::Make(640, 480);
}
void onDraw(SkCanvas* canvas) override {
SkFont font;
font.setSize(30);
const char* text = fApplyKerning ? "Type AWAY" : "Hamburgefons";
const size_t textLen = strlen(text);
SkScalar x = SkIntToScalar(10);
SkScalar dy = font.getMetrics(nullptr);
SkScalar y = dy;
if (fApplyKerning) {
font.setSubpixel(true);
} else {
font.setLinearMetrics(true);
}
SkPaint paint;
for (int i = 0; i < gStylesCount; i++) {
font.setTypeface(fFaces[i]);
canvas->drawSimpleText(text, textLen, SkTextEncoding::kUTF8, x, y, font, paint);
if (fApplyKerning) {
drawKernText(canvas, text, textLen, x + 240, y, font, paint);
}
y += dy;
}
}
private:
using INHERITED = skiagm::GM;
};
DEF_GM( return new TypefaceStylesGM(false); )
DEF_GM( return new TypefaceStylesGM(true); )
////////////////////////////////////////////////////////////////////////////////
static void draw_typeface_rendering_gm(SkCanvas* canvas, sk_sp<SkTypeface> face, SkGlyphID glyph) {
struct AliasType {
SkFont::Edging edging;
bool inLayer;
} constexpr aliasTypes[] {
#ifndef SK_BUILD_FOR_IOS
// This gm crashes on iOS when drawing an embedded bitmap when requesting aliased rendering.
// The crash looks like
// libTrueTypeScaler.dylib`<redacted> + 80
// stop reason = EXC_BAD_ACCESS (code=EXC_ARM_DA_ALIGN, address=...)
// -> 0x330b19d0 <+80>: strd r2, r3, [r5, #36]
// 0x330b19d4 <+84>: movs r3, #0x0
// 0x330b19d6 <+86>: add r2, sp, #0x28
// 0x330b19d8 <+88>: ldr r0, [r4, #0x4]
// Disable testing embedded bitmaps on iOS for now.
// See https://bug.skia.org/5530 .
{ SkFont::Edging::kAlias , false },
#endif
{ SkFont::Edging::kAntiAlias , false },
{ SkFont::Edging::kSubpixelAntiAlias, false },
{ SkFont::Edging::kAntiAlias , true },
{ SkFont::Edging::kSubpixelAntiAlias, true },
};
// The hintgasp.ttf is designed for the following sizes to be different.
// GASP_DOGRAY 0x0002 0<=ppem<=10
// GASP_SYMMETRIC_SMOOTHING 0x0008 0<=ppem<=10
// GASP_GRIDFIT 0x0001 11<=ppem<=12
// GASP_SYMMETRIC_GRIDFIT 0x0004 11<=ppem<=12
// GASP_DOGRAY|GASP_GRIDFIT 0x0003 13<=ppem<=14
// GASP_SYMMETRIC_SMOOTHING|GASP_SYMMETRIC_GRIDFIT 0x000C 13<=ppem<=14
// (neither) 0x0000 15<=ppem
// Odd sizes have embedded bitmaps.
constexpr SkScalar textSizes[] = { 9, 10, 11, 12, 13, 14, 15, 16 };
constexpr SkFontHinting hintingTypes[] = {
SkFontHinting::kNone,
SkFontHinting::kSlight,
SkFontHinting::kNormal,
SkFontHinting::kFull
};
struct SubpixelType {
bool requested;
SkVector offset;
} constexpr subpixelTypes[] = {
{ false, { 0.00, 0.00 } },
{ true , { 0.00, 0.00 } },
{ true , { 0.25, 0.00 } },
{ true , { 0.25, 0.25 } },
};
constexpr bool rotateABitTypes[] = { false, true };
SkScalar y = 0; // The baseline of the previous output
{
SkPaint paint;
SkFont font(face);
font.setEmbeddedBitmaps(true);
SkScalar x = 0;
SkScalar xMax = x;
SkScalar xBase = 0;
for (const SubpixelType subpixel : subpixelTypes) {
y = 0;
font.setSubpixel(subpixel.requested);
for (const AliasType& alias : aliasTypes) {
font.setEdging(alias.edging);
SkAutoCanvasRestore acr1(canvas, false);
if (alias.inLayer) {
canvas->saveLayer(nullptr, &paint);
}
for (const SkScalar& textSize : textSizes) {
x = xBase + 5;
font.setSize(textSize);
SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
y += dy;
for (const SkFontHinting& hinting : hintingTypes) {
font.setHinting(hinting);
for (const bool& rotateABit : rotateABitTypes) {
SkAutoCanvasRestore acr2(canvas, true);
if (rotateABit) {
canvas->rotate(2, x + subpixel.offset.x(),
y + subpixel.offset.y());
}
canvas->drawSimpleText(&glyph, sizeof(glyph), SkTextEncoding::kGlyphID,
x + subpixel.offset.x(),
y + subpixel.offset.y(), font, paint);
SkScalar dx = SkScalarCeilToScalar(font.measureText(
&glyph, sizeof(glyph), SkTextEncoding::kGlyphID)) + 5;
x += dx;
xMax = std::max(x, xMax);
}
}
}
y += 10;
}
xBase = xMax;
}
}
constexpr struct StyleTests {
SkPaint::Style style;
SkScalar strokeWidth;
} styleTypes[] = {
{ SkPaint::kFill_Style, 0.0f},
{ SkPaint::kStroke_Style, 0.0f},
{ SkPaint::kStroke_Style, 0.5f},
{ SkPaint::kStrokeAndFill_Style, 1.0f},
};
constexpr bool fakeBoldTypes[] = { false, true };
{
SkPaint paint;
SkFont font(face, 16);
SkScalar x = 0;
for (const bool& fakeBold : fakeBoldTypes) {
SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
y += dy;
x = 5;
font.setEmbolden(fakeBold);
for (const AliasType& alias : aliasTypes) {
font.setEdging(alias.edging);
SkAutoCanvasRestore acr(canvas, false);
if (alias.inLayer) {
canvas->saveLayer(nullptr, &paint);
}
for (const StyleTests& style : styleTypes) {
paint.setStyle(style.style);
paint.setStrokeWidth(style.strokeWidth);
canvas->drawSimpleText(&glyph, sizeof(glyph), SkTextEncoding::kGlyphID,
x, y, font, paint);
SkScalar dx = SkScalarCeilToScalar(font.measureText(
&glyph, sizeof(glyph), SkTextEncoding::kGlyphID)) + 5;
x += dx;
}
}
y += 10;
}
}
constexpr struct MaskTests {
SkBlurStyle style;
SkScalar sigma;
} maskTypes[] = {
{ SkBlurStyle::kNormal_SkBlurStyle, 0.0f},
{ SkBlurStyle::kSolid_SkBlurStyle, 0.0f},
{ SkBlurStyle::kOuter_SkBlurStyle, 0.0f},
{ SkBlurStyle::kInner_SkBlurStyle, 0.0f},
{ SkBlurStyle::kNormal_SkBlurStyle, 0.5f},
{ SkBlurStyle::kSolid_SkBlurStyle, 0.5f},
{ SkBlurStyle::kOuter_SkBlurStyle, 0.5f},
{ SkBlurStyle::kInner_SkBlurStyle, 0.5f},
{ SkBlurStyle::kNormal_SkBlurStyle, 2.0f},
{ SkBlurStyle::kSolid_SkBlurStyle, 2.0f},
{ SkBlurStyle::kOuter_SkBlurStyle, 2.0f},
{ SkBlurStyle::kInner_SkBlurStyle, 2.0f},
};
{
SkPaint paint;
SkFont font(face, 16);
SkScalar x = 0;
{
for (const AliasType& alias : aliasTypes) {
SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
y += dy;
x = 5;
font.setEdging(alias.edging);
SkAutoCanvasRestore acr(canvas, false);
if (alias.inLayer) {
canvas->saveLayer(nullptr, &paint);
}
for (const MaskTests& mask : maskTypes) {
paint.setMaskFilter(SkMaskFilter::MakeBlur(mask.style, mask.sigma));
canvas->drawSimpleText(&glyph, sizeof(glyph), SkTextEncoding::kGlyphID,
x, y, font, paint);
SkScalar dx = SkScalarCeilToScalar(font.measureText(
&glyph, sizeof(glyph), SkTextEncoding::kGlyphID)) + 5;
x += dx;
}
paint.setMaskFilter(nullptr);
}
y += 10;
}
}
}
DEF_SIMPLE_GM_CAN_FAIL(typefacerendering, canvas, errMsg, 640, 840) {
sk_sp<SkTypeface> face = MakeResourceAsTypeface("fonts/hintgasp.ttf");
if (!face) {
return skiagm::DrawResult::kSkip;
}
draw_typeface_rendering_gm(canvas, face, face->unicharToGlyph('A'));
// Should draw nothing and not do anything undefined.
draw_typeface_rendering_gm(canvas, face, 0xFFFF);
return skiagm::DrawResult::kOk;
}
// Type1 fonts don't currently work in Skia on Windows.
#ifndef SK_BUILD_FOR_WIN
DEF_SIMPLE_GM_CAN_FAIL(typefacerendering_pfa, canvas, errMsg, 640, 840) {
sk_sp<SkTypeface> face = MakeResourceAsTypeface("fonts/Roboto2-Regular.pfa");
if (!face) {
return skiagm::DrawResult::kSkip;
}
draw_typeface_rendering_gm(canvas, face, face->unicharToGlyph('O'));
return skiagm::DrawResult::kOk;
}
DEF_SIMPLE_GM_CAN_FAIL(typefacerendering_pfb, canvas, errMsg, 640, 840) {
sk_sp<SkTypeface> face = MakeResourceAsTypeface("fonts/Roboto2-Regular.pfb");
if (!face) {
return skiagm::DrawResult::kSkip;
}
draw_typeface_rendering_gm(canvas, face, face->unicharToGlyph('O'));
return skiagm::DrawResult::kOk;
}
#endif
////////////////////////////////////////////////////////////////////////////////
#include "include/effects/SkStrokeAndFillPathEffect.h"
// Exercise different paint styles and embolden, and compare with strokeandfill patheffect
DEF_SIMPLE_GM(typeface_styling, canvas, 710, 360) {
sk_sp<SkTypeface> face = MakeResourceAsTypeface("fonts/Roboto-Regular.ttf");
SkFont font;
font.setTypeface(face);
font.setSize(100);
font.setEdging(SkFont::Edging::kAntiAlias);
uint16_t glyphs[1] = { font.unicharToGlyph('A') };
SkPoint pos[1] = { {0, 0} };
auto draw = [&](SkPaint::Style style, float width, sk_sp<SkPathEffect> pe) {
// Draws 3 rows:
// 1. normal
// 2. emboldened
// 3. normal(white) on top of emboldened (to show the delta)
SkPaint paint;
paint.setStyle(style);
paint.setStrokeWidth(width);
paint.setPathEffect(pe);
font.setEmbolden(true);
canvas->drawGlyphs(1, glyphs, pos, {20, 120*2}, font, paint);
canvas->drawGlyphs(1, glyphs, pos, {20, 120*3}, font, paint);
font.setEmbolden(false);
canvas->drawGlyphs(1, glyphs, pos, {20, 120*1}, font, paint);
paint.setColor(SK_ColorYELLOW);
canvas->drawGlyphs(1, glyphs, pos, {20, 120*3}, font, paint);
};
const struct {
SkPaint::Style style;
float width;
bool usePE;
} recs[] = {
{ SkPaint::kFill_Style, 0, false },
{ SkPaint::kStroke_Style, 0, false },
{ SkPaint::kStroke_Style, 3, false },
{ SkPaint::kStrokeAndFill_Style, 0, false },
{ SkPaint::kStrokeAndFill_Style, 3, false },
{ SkPaint::kStroke_Style, 0, true },
{ SkPaint::kStroke_Style, 3, true },
};
canvas->translate(0, -20);
auto pe = SkStrokeAndFillPathEffect::Make();
for (auto r : recs) {
draw(r.style, r.width, r.usePE ? pe : nullptr);
canvas->translate(100, 0);
}
}