void operator()(const char text[], size_t length, SkScalar x, SkScalar y, const SkPaint& paint) override { SkPaint p(paint); p.setTextEncoding(SkPaint::kGlyphID_TextEncoding); const int count = paint.countText(text, length); paint.textToGlyphs(text, length, fBuilder.allocRun(p, count, x, y).glyphs); }
static void AddRun(const SkFont& font, int count, SkTextBlobRunIterator::GlyphPositioning pos, const SkPoint& offset, SkTextBlobBuilder& builder, const SkRect* bounds = nullptr) { switch (pos) { case SkTextBlobRunIterator::kDefault_Positioning: { const SkTextBlobBuilder::RunBuffer& rb = builder.allocRun(font, count, offset.x(), offset.y(), bounds); for (int i = 0; i < count; ++i) { rb.glyphs[i] = i; } } break; case SkTextBlobRunIterator::kHorizontal_Positioning: { const SkTextBlobBuilder::RunBuffer& rb = builder.allocRunPosH(font, count, offset.y(), bounds); for (int i = 0; i < count; ++i) { rb.glyphs[i] = i; rb.pos[i] = SkIntToScalar(i); } } break; case SkTextBlobRunIterator::kFull_Positioning: { const SkTextBlobBuilder::RunBuffer& rb = builder.allocRunPos(font, count, bounds); for (int i = 0; i < count; ++i) { rb.glyphs[i] = i; rb.pos[i * 2] = SkIntToScalar(i); rb.pos[i * 2 + 1] = -SkIntToScalar(i); } } break; default: SK_ABORT("unhandled positioning value"); } }
SkRect Text::onRevalidate(InvalidationController*, const SkMatrix&) { // TODO: we could potentially track invals which don't require rebuilding the blob. SkPaint font; font.setFlags(fFlags); font.setTypeface(fTypeface); font.setTextSize(fSize); font.setTextScaleX(fScaleX); font.setTextSkewX(fSkewX); font.setTextAlign(fAlign); font.setHinting(fHinting); // First, convert to glyphIDs. font.setTextEncoding(SkPaint::kUTF8_TextEncoding); SkSTArray<256, SkGlyphID, true> glyphs; glyphs.reset(font.textToGlyphs(fText.c_str(), fText.size(), nullptr)); SkAssertResult(font.textToGlyphs(fText.c_str(), fText.size(), glyphs.begin()) == glyphs.count()); font.setTextEncoding(SkPaint::kGlyphID_TextEncoding); // Next, build the cached blob. SkTextBlobBuilder builder; const auto& buf = builder.allocRun(font, glyphs.count(), 0, 0, nullptr); if (!buf.glyphs) { fBlob.reset(); return SkRect::MakeEmpty(); } memcpy(buf.glyphs, glyphs.begin(), glyphs.count() * sizeof(SkGlyphID)); fBlob = builder.make(); return fBlob ? fBlob->bounds().makeOffset(fPosition.x(), fPosition.y()) : SkRect::MakeEmpty(); }
void draw(SkCanvas* canvas) { SkTextBlobBuilder textBlobBuilder; SkFont font; font.setSize(50); const SkTextBlobBuilder::RunBuffer& run = textBlobBuilder.allocRun(font, 1, 20, 100); run.glyphs[0] = 20; sk_sp<const SkTextBlob> blob = textBlobBuilder.make(); SkPaint paint; paint.setColor(SK_ColorBLUE); canvas->drawTextBlob(blob.get(), 0, 0, paint); }
void onDelayedSetup() override { fTypeface.reset(sk_tool_utils::create_portable_typeface("serif", SkTypeface::kNormal)); // make textblob SkPaint paint; paint.setTypeface(fTypeface); const char* text = "Hello blob!"; SkTDArray<uint16_t> glyphs; size_t len = strlen(text); glyphs.append(paint.textToGlyphs(text, len, nullptr)); paint.textToGlyphs(text, len, glyphs.begin()); SkTextBlobBuilder builder; paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(paint, glyphs.count(), 10, 10, nullptr); memcpy(run.glyphs, glyphs.begin(), glyphs.count() * sizeof(uint16_t)); fBlob.reset(builder.build()); }
// This test hammers the GPU textblobcache and font atlas static void text_blob_cache_inner(skiatest::Reporter* reporter, GrContext* context, int maxTotalText, int maxGlyphID, int maxFamilies, bool normal, bool stressTest) { // setup surface uint32_t flags = 0; SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType); // configure our context for maximum stressing of cache and atlas if (stressTest) { GrTest::SetupAlwaysEvictAtlas(context); context->setTextBlobCacheLimit_ForTesting(0); } SkImageInfo info = SkImageInfo::Make(kWidth, kHeight, kN32_SkColorType, kPremul_SkAlphaType); auto surface(SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, &props)); REPORTER_ASSERT(reporter, surface); if (!surface) { return; } SkCanvas* canvas = surface->getCanvas(); sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); int count = SkMin32(fm->countFamilies(), maxFamilies); // make a ton of text SkAutoTArray<uint16_t> text(maxTotalText); for (int i = 0; i < maxTotalText; i++) { text[i] = i % maxGlyphID; } // generate textblobs SkTArray<sk_sp<SkTextBlob>> blobs; for (int i = 0; i < count; i++) { SkPaint paint; paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); paint.setTextSize(48); // draw big glyphs to really stress the atlas SkString familyName; fm->getFamilyName(i, &familyName); sk_sp<SkFontStyleSet> set(fm->createStyleSet(i)); for (int j = 0; j < set->count(); ++j) { SkFontStyle fs; set->getStyle(j, &fs, nullptr); // We use a typeface which randomy returns unexpected mask formats to fuzz sk_sp<SkTypeface> orig(set->createTypeface(j)); if (normal) { paint.setTypeface(orig); } else { paint.setTypeface(sk_make_sp<SkRandomTypeface>(orig, paint, true)); } SkTextBlobBuilder builder; for (int aa = 0; aa < 2; aa++) { for (int subpixel = 0; subpixel < 2; subpixel++) { for (int lcd = 0; lcd < 2; lcd++) { paint.setAntiAlias(SkToBool(aa)); paint.setSubpixelText(SkToBool(subpixel)); paint.setLCDRenderText(SkToBool(lcd)); if (!SkToBool(lcd)) { paint.setTextSize(160); } const SkTextBlobBuilder::RunBuffer& run = builder.allocRun(paint, maxTotalText, 0, 0, nullptr); memcpy(run.glyphs, text.get(), maxTotalText * sizeof(uint16_t)); } } } blobs.emplace_back(builder.make()); } } // create surface where LCD is impossible info = SkImageInfo::MakeN32Premul(kWidth, kHeight); SkSurfaceProps propsNoLCD(0, kUnknown_SkPixelGeometry); auto surfaceNoLCD(canvas->makeSurface(info, &propsNoLCD)); REPORTER_ASSERT(reporter, surface); if (!surface) { return; } SkCanvas* canvasNoLCD = surfaceNoLCD->getCanvas(); // test redraw draw(canvas, 2, blobs); draw(canvasNoLCD, 2, blobs); // test draw after free context->freeGpuResources(); draw(canvas, 1, blobs); context->freeGpuResources(); draw(canvasNoLCD, 1, blobs); // test draw after abandon context->abandonContext(); draw(canvas, 1, blobs); }
sk_sp<SkTextBlob> makeBlob(unsigned blobIndex) { SkTextBlobBuilder builder; SkFont font; font.setSubpixel(true); font.setEdging(SkFont::Edging::kAntiAlias); font.setTypeface(fTypeface); for (unsigned l = 0; l < SK_ARRAY_COUNT(blobConfigs[blobIndex]); ++l) { unsigned currentGlyph = 0; for (unsigned c = 0; c < SK_ARRAY_COUNT(blobConfigs[blobIndex][l]); ++c) { const BlobCfg* cfg = &blobConfigs[blobIndex][l][c]; unsigned count = cfg->count; if (count > fGlyphs.count() - currentGlyph) { count = fGlyphs.count() - currentGlyph; } if (0 == count) { break; } font.setSize(kFontSize * cfg->scale); const SkScalar advanceX = font.getSize() * 0.85f; const SkScalar advanceY = font.getSize() * 1.5f; SkPoint offset = SkPoint::Make(currentGlyph * advanceX + c * advanceX, advanceY * l); switch (cfg->pos) { case kDefault_Pos: { const SkTextBlobBuilder::RunBuffer& buf = builder.allocRun(font, count, offset.x(), offset.y()); memcpy(buf.glyphs, fGlyphs.begin() + currentGlyph, count * sizeof(uint16_t)); } break; case kScalar_Pos: { const SkTextBlobBuilder::RunBuffer& buf = builder.allocRunPosH(font, count, offset.y()); SkTDArray<SkScalar> pos; for (unsigned i = 0; i < count; ++i) { *pos.append() = offset.x() + i * advanceX; } memcpy(buf.glyphs, fGlyphs.begin() + currentGlyph, count * sizeof(uint16_t)); memcpy(buf.pos, pos.begin(), count * sizeof(SkScalar)); } break; case kPoint_Pos: { const SkTextBlobBuilder::RunBuffer& buf = builder.allocRunPos(font, count); SkTDArray<SkScalar> pos; for (unsigned i = 0; i < count; ++i) { *pos.append() = offset.x() + i * advanceX; *pos.append() = offset.y() + i * (advanceY / count); } memcpy(buf.glyphs, fGlyphs.begin() + currentGlyph, count * sizeof(uint16_t)); memcpy(buf.pos, pos.begin(), count * sizeof(SkScalar) * 2); } break; default: SK_ABORT("unhandled pos value"); } currentGlyph += count; } } return builder.make(); }
// This unit test verifies blob bounds computation. static void TestBounds(skiatest::Reporter* reporter) { SkTextBlobBuilder builder; SkFont font; // Explicit bounds. { sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, !blob); } { SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); builder.allocRun(font, 16, 0, 0, &r1); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds() == r1); } { SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); builder.allocRunPosH(font, 16, 0, &r1); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds() == r1); } { SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); builder.allocRunPos(font, 16, &r1); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds() == r1); } { SkRect r1 = SkRect::MakeXYWH(10, 10, 20, 20); SkRect r2 = SkRect::MakeXYWH(15, 20, 50, 50); SkRect r3 = SkRect::MakeXYWH(0, 5, 10, 5); builder.allocRun(font, 16, 0, 0, &r1); builder.allocRunPosH(font, 16, 0, &r2); builder.allocRunPos(font, 16, &r3); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds() == SkRect::MakeXYWH(0, 5, 65, 65)); } { sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, !blob); } // Implicit bounds { // Exercise the empty bounds path, and ensure that RunRecord-aligned pos buffers // don't trigger asserts (http://crbug.com/542643). SkFont font; font.setSize(0); const char* txt = "BOOO"; const size_t txtLen = strlen(txt); const int glyphCount = font.countText(txt, txtLen, SkTextEncoding::kUTF8); const SkTextBlobBuilder::RunBuffer& buffer = builder.allocRunPos(font, glyphCount); font.textToGlyphs(txt, txtLen, SkTextEncoding::kUTF8, buffer.glyphs, glyphCount); memset(buffer.pos, 0, sizeof(SkScalar) * glyphCount * 2); sk_sp<SkTextBlob> blob(builder.make()); REPORTER_ASSERT(reporter, blob->bounds().isEmpty()); } }