blob: d98740b4201deef911196e0e711bdd6827a4ba53 [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkRemoteGlyphCache.h"
#include <iterator>
#include <memory>
#include <string>
#include <tuple>
#include "SkDevice.h"
#include "SkFindAndPlaceGlyph.h"
#include "SkStrikeCache.h"
#include "SkTextBlobRunIterator.h"
#include "SkTraceEvent.h"
#include "SkTypeface_remote.h"
static SkDescriptor* auto_descriptor_from_desc(const SkDescriptor* source_desc,
SkFontID font_id,
SkAutoDescriptor* ad) {
ad->reset(source_desc->getLength());
auto* desc = ad->getDesc();
desc->init();
// Rec.
{
uint32_t size;
auto ptr = source_desc->findEntry(kRec_SkDescriptorTag, &size);
SkScalerContextRec rec;
std::memcpy(&rec, ptr, size);
rec.fFontID = font_id;
desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
}
// Path effect.
{
uint32_t size;
auto ptr = source_desc->findEntry(kPathEffect_SkDescriptorTag, &size);
if (ptr) desc->addEntry(kPathEffect_SkDescriptorTag, size, ptr);
}
// Mask filter.
{
uint32_t size;
auto ptr = source_desc->findEntry(kMaskFilter_SkDescriptorTag, &size);
if (ptr) desc->addEntry(kMaskFilter_SkDescriptorTag, size, ptr);
}
desc->computeChecksum();
return desc;
}
template <typename T>
class ArraySlice final : public std::tuple<const T*, size_t> {
public:
// Additional constructors as needed.
ArraySlice(const T* data, size_t size) : fData{data}, fSize{size} { }
ArraySlice() : ArraySlice<T>(nullptr, 0) { }
const T* begin() {
return this->data();
}
const T* end() {
return &this->data()[this->size()];
}
const T* data() const {
return fData;
}
size_t size() const {
return fSize;
}
private:
const T* fData;
size_t fSize;
};
// -- Serializer ----------------------------------------------------------------------------------
size_t pad(size_t size, size_t alignment) { return (size + (alignment - 1)) & ~(alignment - 1); }
class Serializer {
public:
Serializer(std::vector<uint8_t>* buffer) : fBuffer{buffer} { }
template <typename T, typename... Args>
T* emplace(Args&&... args) {
auto result = allocate(sizeof(T), alignof(T));
return new (result) T{std::forward<Args>(args)...};
}
template <typename T>
void write(const T& data) {
T* result = (T*)allocate(sizeof(T), alignof(T));
memcpy(result, &data, sizeof(T));
}
template <typename T>
T* allocate() {
T* result = (T*)allocate(sizeof(T), alignof(T));
return result;
}
void writeDescriptor(const SkDescriptor& desc) {
write(desc.getLength());
auto result = allocate(desc.getLength(), alignof(SkDescriptor));
memcpy(result, &desc, desc.getLength());
}
template <typename T>
T* allocateArray(int count) {
auto result = allocate(sizeof(T) * count, alignof(T));
return new (result) T[count];
}
private:
void* allocate(size_t size, size_t alignment) {
size_t aligned = pad(fBuffer->size(), alignment);
fBuffer->resize(aligned + size);
return &(*fBuffer)[aligned];
}
std::vector<uint8_t>* fBuffer;
};
// -- Deserializer -------------------------------------------------------------------------------
// Note that the Deserializer is reading untrusted data, we need to guard against invalid data.
class Deserializer {
public:
Deserializer(const volatile char* memory, size_t memorySize)
: fMemory(memory), fMemorySize(memorySize) {}
template <typename T>
bool read(T* val) {
auto* result = this->ensureAtLeast(sizeof(T), alignof(T));
if (!result) return false;
memcpy(val, const_cast<const char*>(result), sizeof(T));
return true;
}
bool readDescriptor(SkAutoDescriptor* ad) {
uint32_t desc_length = 0u;
if (!read<uint32_t>(&desc_length)) return false;
auto* result = this->ensureAtLeast(desc_length, alignof(SkDescriptor));
if (!result) return false;
ad->reset(desc_length);
memcpy(ad->getDesc(), const_cast<const char*>(result), desc_length);
return true;
}
template <typename T>
ArraySlice<T> readArray(int count) {
size_t size = count * sizeof(T);
const T* base = (const T*)this->ensureAtLeast(size, alignof(T));
if (!base) return ArraySlice<T>();
ArraySlice<T> result = ArraySlice<T>{base, (uint32_t)count};
return result;
}
private:
const volatile char* ensureAtLeast(size_t size, size_t alignment) {
size_t padded = pad(fBytesRead, alignment);
// Not enough data
if (padded + size > fMemorySize) return nullptr;
auto* result = fMemory + padded;
fBytesRead = padded + size;
return result;
}
// Note that we read each piece of memory only once to guard against TOCTOU violations.
const volatile char* fMemory;
size_t fMemorySize;
size_t fBytesRead = 0u;
};
size_t SkDescriptorMapOperators::operator()(const SkDescriptor* key) const {
return key->getChecksum();
}
bool SkDescriptorMapOperators::operator()(const SkDescriptor* lhs,
const SkDescriptor* rhs) const {
return *lhs == *rhs;
}
// -- TrackLayerDevice -----------------------------------------------------------------------------
class TrackLayerDevice : public SkNoPixelsDevice {
public:
TrackLayerDevice(const SkIRect& bounds, const SkSurfaceProps& props)
: SkNoPixelsDevice(bounds, props) { }
SkBaseDevice* onCreateDevice(const CreateInfo& cinfo, const SkPaint*) override {
const SkSurfaceProps surfaceProps(this->surfaceProps().flags(), cinfo.fPixelGeometry);
return new TrackLayerDevice(this->getGlobalBounds(), surfaceProps);
}
};
// -- SkTextBlobCacheDiffCanvas -------------------------------------------------------------------
SkTextBlobCacheDiffCanvas::SkTextBlobCacheDiffCanvas(int width, int height,
const SkMatrix& deviceMatrix,
const SkSurfaceProps& props,
SkStrikeServer* strikeSever)
: SkNoDrawCanvas{sk_make_sp<TrackLayerDevice>(SkIRect::MakeWH(width, height), props)}
, fDeviceMatrix{deviceMatrix}
, fSurfaceProps{props}
, fStrikeServer{strikeSever} {
SkASSERT(fStrikeServer);
}
SkTextBlobCacheDiffCanvas::~SkTextBlobCacheDiffCanvas() = default;
SkCanvas::SaveLayerStrategy SkTextBlobCacheDiffCanvas::getSaveLayerStrategy(
const SaveLayerRec&rec)
{
return kFullLayer_SaveLayerStrategy;
}
void SkTextBlobCacheDiffCanvas::onDrawTextBlob(
const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) {
SkPoint position{x, y};
SkPaint runPaint{paint};
SkTextBlobRunIterator it(blob);
for (;!it.done(); it.next()) {
// applyFontToPaint() always overwrites the exact same attributes,
// so it is safe to not re-seed the paint for this reason.
it.applyFontToPaint(&runPaint);
if (auto looper = runPaint.getLooper()) {
this->processLooper(position, it, runPaint, looper);
} else {
this->processGlyphRun(position, it, runPaint);
}
}
}
void SkTextBlobCacheDiffCanvas::processLooper(
const SkPoint& position,
const SkTextBlobRunIterator& it,
const SkPaint& origPaint,
SkDrawLooper* looper)
{
SkSTArenaAlloc<48> alloc;
auto context = looper->makeContext(this, &alloc);
SkPaint runPaint = origPaint;
while (context->next(this, &runPaint)) {
this->save();
this->processGlyphRun(position, it, runPaint);
this->restore();
runPaint = origPaint;
}
}
#define FAIL_AND_RETURN \
SkDEBUGFAIL("Failed to process glyph run"); \
return;
void SkTextBlobCacheDiffCanvas::processGlyphRun(
const SkPoint& position,
const SkTextBlobRunIterator& it,
const SkPaint& runPaint)
{
if (runPaint.getTextEncoding() != SkPaint::TextEncoding::kGlyphID_TextEncoding) {
TRACE_EVENT0("skia", "kGlyphID_TextEncoding");
FAIL_AND_RETURN
}
// All other alignment modes need the glyph advances. Use the slow drawing mode.
if (runPaint.getTextAlign() != SkPaint::kLeft_Align) {
TRACE_EVENT0("skia", "kLeft_Align");
FAIL_AND_RETURN
}
using PosFn = SkPoint(*)(int index, const SkScalar* pos);
PosFn posFn;
switch (it.positioning()) {
case SkTextBlob::kDefault_Positioning: {
// Default positioning needs advances. Can't do that.
TRACE_EVENT0("skia", "kDefault_Positioning");
FAIL_AND_RETURN
}
case SkTextBlob::kHorizontal_Positioning:
posFn = [](int index, const SkScalar* pos) {
return SkPoint{pos[index], 0};
};
break;
case SkTextBlob::kFull_Positioning:
posFn = [](int index, const SkScalar* pos) {
return SkPoint{pos[2 * index], pos[2 * index + 1]};
};
break;
default:
posFn = nullptr;
SK_ABORT("unhandled positioning mode");
}
SkMatrix blobMatrix{fDeviceMatrix};
blobMatrix.preConcat(this->getTotalMatrix());
if (blobMatrix.hasPerspective()) {
TRACE_EVENT0("skia", "hasPerspective");
FAIL_AND_RETURN
}
blobMatrix.preTranslate(position.x(), position.y());
SkMatrix runMatrix{blobMatrix};
runMatrix.preTranslate(it.offset().x(), it.offset().y());
using MapFn = SkPoint(*)(const SkMatrix& m, SkPoint pt);
MapFn mapFn;
switch ((int)runMatrix.getType()) {
case SkMatrix::kIdentity_Mask:
case SkMatrix::kTranslate_Mask:
mapFn = [](const SkMatrix& m, SkPoint pt) {
pt.offset(m.getTranslateX(), m.getTranslateY());
return pt;
};
break;
case SkMatrix::kScale_Mask:
case SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask:
mapFn = [](const SkMatrix& m, SkPoint pt) {
return SkPoint{pt.x() * m.getScaleX() + m.getTranslateX(),
pt.y() * m.getScaleY() + m.getTranslateY()};
};
break;
case SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask:
case SkMatrix::kAffine_Mask | SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask:
mapFn = [](const SkMatrix& m, SkPoint pt) {
return SkPoint{
pt.x() * m.getScaleX() + pt.y() * m.getSkewX() + m.getTranslateX(),
pt.x() * m.getSkewY() + pt.y() * m.getScaleY() + m.getTranslateY()};
};
break;
default:
mapFn = nullptr;
SK_ABORT("Bad matrix.");
}
SkScalerContextRec rec;
SkScalerContextEffects effects;
// TODO(crbug.com/831354): The typeface proxy on the client does not replicate the
// filtering done by the typeface on the server.
const bool enableTypefaceFiltering = false;
SkScalerContext::MakeRecAndEffects(runPaint, &fSurfaceProps, &runMatrix,
SkScalerContextFlags::kFakeGammaAndBoostContrast, &rec,
&effects, enableTypefaceFiltering);
TRACE_EVENT1("skia", "RecForDesc", "rec", TRACE_STR_COPY(rec.dump().c_str()));
auto desc = SkScalerContext::DescriptorGivenRecAndEffects(rec, effects);
auto* glyphCacheState = static_cast<SkStrikeServer*>(fStrikeServer)
->getOrCreateCache(runPaint.getTypeface(), std::move(desc));
SkASSERT(glyphCacheState);
bool isSubpixel = SkToBool(rec.fFlags & SkScalerContext::kSubpixelPositioning_Flag);
SkAxisAlignment axisAlignment = SkAxisAlignment::kNone_SkAxisAlignment;
if (it.positioning() == SkTextBlob::kHorizontal_Positioning) {
axisAlignment = rec.computeAxisAlignmentForHText();
}
auto pos = it.pos();
const uint16_t* glyphs = it.glyphs();
for (uint32_t index = 0; index < it.glyphCount(); index++) {
SkIPoint subPixelPos{0, 0};
if (runPaint.isAntiAlias() && isSubpixel) {
SkPoint glyphPos = mapFn(runMatrix, posFn(index, pos));
subPixelPos = SkFindAndPlaceGlyph::SubpixelAlignment(axisAlignment, glyphPos);
}
glyphCacheState->addGlyph(runPaint.getTypeface(),
effects,
SkPackedGlyphID(glyphs[index], subPixelPos.x(), subPixelPos.y()));
}
}
struct StrikeSpec {
StrikeSpec() {}
StrikeSpec(SkFontID typefaceID_, size_t glyphCount_, SkDiscardableHandleId discardableHandleId_)
: typefaceID{typefaceID_}
, glyphCount{glyphCount_}
, discardableHandleId(discardableHandleId_) {}
SkFontID typefaceID = 0u;
size_t glyphCount = 0u;
SkDiscardableHandleId discardableHandleId = 0u;
/* desc */
/* n X (glyphs ids) */
};
struct WireTypeface {
WireTypeface() = default;
WireTypeface(SkFontID typeface_id, int glyph_count, SkFontStyle style, bool is_fixed)
: typefaceID(typeface_id), glyphCount(glyph_count), style(style), isFixed(is_fixed) {}
// std::thread::id thread_id; // TODO:need to figure a good solution
SkFontID typefaceID;
int glyphCount;
SkFontStyle style;
bool isFixed;
};
// SkStrikeServer -----------------------------------------
SkStrikeServer::SkStrikeServer(DiscardableHandleManager* discardableHandleManager)
: fDiscardableHandleManager(discardableHandleManager) {
SkASSERT(fDiscardableHandleManager);
}
SkStrikeServer::~SkStrikeServer() = default;
sk_sp<SkData> SkStrikeServer::serializeTypeface(SkTypeface* tf) {
WireTypeface wire(SkTypeface::UniqueID(tf), tf->countGlyphs(), tf->fontStyle(),
tf->isFixedPitch());
return SkData::MakeWithCopy(&wire, sizeof(wire));
}
void SkStrikeServer::writeStrikeData(std::vector<uint8_t>* memory) {
if (fLockedDescs.empty() && fTypefacesToSend.empty()) return;
Serializer serializer(memory);
serializer.emplace<size_t>(fTypefacesToSend.size());
for (const auto& tf : fTypefacesToSend) serializer.write<WireTypeface>(tf);
fTypefacesToSend.clear();
serializer.emplace<size_t>(fLockedDescs.size());
for (const auto* desc : fLockedDescs) {
auto it = fRemoteGlyphStateMap.find(desc);
SkASSERT(it != fRemoteGlyphStateMap.end());
// TODO: This is unnecessary, write only the descs which has any glyphs
// to send. It was getting awkward to write the size after writing the
// descs because the vector reallocs.
serializer.emplace<bool>(it->second->has_pending_glyphs());
if (!it->second->has_pending_glyphs()) continue;
it->second->writePendingGlyphs(&serializer);
}
fLockedDescs.clear();
}
SkStrikeServer::SkGlyphCacheState* SkStrikeServer::getOrCreateCache(
SkTypeface* tf, std::unique_ptr<SkDescriptor> desc) {
SkASSERT(desc);
// Already locked.
if (fLockedDescs.find(desc.get()) != fLockedDescs.end()) {
auto it = fRemoteGlyphStateMap.find(desc.get());
SkASSERT(it != fRemoteGlyphStateMap.end());
return it->second.get();
}
// Try to lock.
auto it = fRemoteGlyphStateMap.find(desc.get());
if (it != fRemoteGlyphStateMap.end()) {
bool locked = fDiscardableHandleManager->lockHandle(it->second->discardable_handle_id());
if (locked) {
fLockedDescs.insert(it->first);
return it->second.get();
}
// If the lock failed, the entry was deleted on the client. Remove our
// tracking.
fRemoteGlyphStateMap.erase(it);
}
const SkFontID typeface_id = tf->uniqueID();
if (!fCachedTypefaces.contains(typeface_id)) {
fCachedTypefaces.add(typeface_id);
fTypefacesToSend.emplace_back(typeface_id, tf->countGlyphs(), tf->fontStyle(),
tf->isFixedPitch());
}
auto* desc_ptr = desc.get();
auto new_handle = fDiscardableHandleManager->createHandle();
auto cache_state = skstd::make_unique<SkGlyphCacheState>(std::move(desc), new_handle);
auto* cache_state_ptr = cache_state.get();
fLockedDescs.insert(desc_ptr);
fRemoteGlyphStateMap[desc_ptr] = std::move(cache_state);
return cache_state_ptr;
}
SkStrikeServer::SkGlyphCacheState::SkGlyphCacheState(std::unique_ptr<SkDescriptor> desc,
uint32_t discardable_handle_id)
: fDesc(std::move(desc)), fDiscardableHandleId(discardable_handle_id) {
SkASSERT(fDesc);
}
SkStrikeServer::SkGlyphCacheState::~SkGlyphCacheState() = default;
void SkStrikeServer::SkGlyphCacheState::addGlyph(SkTypeface* typeface,
const SkScalerContextEffects& effects,
SkPackedGlyphID glyph) {
// Already cached.
if (fCachedGlyphs.contains(glyph)) return;
// Serialize and cache. Also create the scalar context to use when serializing
// this glyph.
fCachedGlyphs.add(glyph);
fPendingGlyphs.push_back(glyph);
if (!fContext) fContext = typeface->createScalerContext(effects, fDesc.get(), false);
}
void SkStrikeServer::SkGlyphCacheState::writePendingGlyphs(Serializer* serializer) {
// Write the desc.
serializer->emplace<StrikeSpec>(fContext->getTypeface()->uniqueID(), fPendingGlyphs.size(),
fDiscardableHandleId);
serializer->writeDescriptor(*fDesc.get());
// Write FontMetrics.
SkPaint::FontMetrics fontMetrics;
fContext->getFontMetrics(&fontMetrics);
serializer->write<SkPaint::FontMetrics>(fontMetrics);
// Write Glyphs.
for (const auto& glyphID : fPendingGlyphs) {
auto glyph = serializer->emplace<SkGlyph>();
glyph->initWithGlyphID(glyphID);
fContext->getMetrics(glyph);
auto imageSize = glyph->computeImageSize();
glyph->fPathData = nullptr;
glyph->fImage = nullptr;
if (imageSize > 0) {
// Since the allocateArray can move glyph, make one that stays in one place.
SkGlyph stationaryGlyph = *glyph;
stationaryGlyph.fImage = serializer->allocateArray<uint8_t>(imageSize);
fContext->getImage(stationaryGlyph);
}
}
// Note that we reset the context after serializing pending glyphs since we
// don't want to extend the lifetime of the typeface.
fPendingGlyphs.clear();
fContext.reset();
}
// SkStrikeClient -----------------------------------------
class SkStrikeClient::DiscardableStrikePinner : public SkStrikePinner {
public:
DiscardableStrikePinner(SkDiscardableHandleId discardableHandleId,
sk_sp<DiscardableHandleManager> manager)
: fDiscardableHandleId(discardableHandleId), fManager(std::move(manager)) {}
~DiscardableStrikePinner() override = default;
bool canDelete() override { return fManager->deleteHandle(fDiscardableHandleId); }
private:
const SkDiscardableHandleId fDiscardableHandleId;
sk_sp<DiscardableHandleManager> fManager;
};
SkStrikeClient::SkStrikeClient(sk_sp<DiscardableHandleManager> discardableManager)
: fDiscardableHandleManager(std::move(discardableManager)) {}
SkStrikeClient::~SkStrikeClient() = default;
#define READ_FAILURE \
{ \
SkDEBUGFAIL("Bad serialization"); \
return false; \
}
bool SkStrikeClient::readStrikeData(const volatile void* memory, size_t memorySize) {
SkASSERT(memorySize != 0u);
Deserializer deserializer(static_cast<const volatile char*>(memory), memorySize);
size_t typefaceSize = 0u;
if (!deserializer.read<size_t>(&typefaceSize)) READ_FAILURE
for (size_t i = 0; i < typefaceSize; ++i) {
WireTypeface wire;
if (!deserializer.read<WireTypeface>(&wire)) READ_FAILURE
// TODO(khushalsagar): The typeface no longer needs a reference to the
// SkStrikeClient, since all needed glyphs must have been pushed before
// raster.
addTypeface(wire);
}
size_t strikeCount = 0u;
if (!deserializer.read<size_t>(&strikeCount)) READ_FAILURE
for (size_t i = 0; i < strikeCount; ++i) {
bool has_glyphs = false;
if (!deserializer.read<bool>(&has_glyphs)) READ_FAILURE
if (!has_glyphs) continue;
StrikeSpec spec;
if (!deserializer.read<StrikeSpec>(&spec)) READ_FAILURE
SkAutoDescriptor sourceAd;
if (!deserializer.readDescriptor(&sourceAd)) READ_FAILURE
SkPaint::FontMetrics fontMetrics;
if (!deserializer.read<SkPaint::FontMetrics>(&fontMetrics)) READ_FAILURE
// Get the local typeface from remote fontID.
auto* tf = fRemoteFontIdToTypeface.find(spec.typefaceID)->get();
// Received strikes for a typeface which doesn't exist.
if (!tf) READ_FAILURE
// Replace the ContextRec in the desc from the server to create the client
// side descriptor.
// TODO: Can we do this in-place and re-compute checksum? Instead of a complete copy.
SkAutoDescriptor ad;
auto* client_desc = auto_descriptor_from_desc(sourceAd.getDesc(), tf->uniqueID(), &ad);
auto strike = SkStrikeCache::FindStrikeExclusive(*client_desc);
if (strike == nullptr) {
// Note that we don't need to deserialize the effects since we won't be generating any
// glyphs here anyway, and the desc is still correct since it includes the serialized
// effects.
SkScalerContextEffects effects;
auto scaler = SkStrikeCache::CreateScalerContext(*client_desc, effects, *tf);
strike = SkStrikeCache::CreateStrikeExclusive(
*client_desc, std::move(scaler), &fontMetrics,
skstd::make_unique<DiscardableStrikePinner>(spec.discardableHandleId,
fDiscardableHandleManager));
}
for (size_t j = 0; j < spec.glyphCount; j++) {
SkGlyph glyph;
if (!deserializer.read<SkGlyph>(&glyph)) READ_FAILURE
SkGlyph* allocatedGlyph = strike->getRawGlyphByID(glyph.getPackedID());
*allocatedGlyph = glyph;
ArraySlice<uint8_t> image;
auto imageSize = glyph.computeImageSize();
if (imageSize != 0) {
image = deserializer.readArray<uint8_t>(imageSize);
if (!image.data()) READ_FAILURE
allocatedGlyph->allocImage(strike->getAlloc());
memcpy(allocatedGlyph->fImage, image.data(), image.size());
}
}
}
return true;
}
sk_sp<SkTypeface> SkStrikeClient::deserializeTypeface(const void* buf, size_t len) {
WireTypeface wire;
if (len != sizeof(wire)) return nullptr;
memcpy(&wire, buf, sizeof(wire));
return addTypeface(wire);
}
sk_sp<SkTypeface> SkStrikeClient::addTypeface(const WireTypeface& wire) {
auto* typeface = fRemoteFontIdToTypeface.find(wire.typefaceID);
if (typeface) return *typeface;
auto newTypeface = sk_make_sp<SkTypefaceProxy>(wire.typefaceID, wire.glyphCount, wire.style,
wire.isFixed, this);
fRemoteFontIdToTypeface.set(wire.typefaceID, newTypeface);
return std::move(newTypeface);
}
void SkStrikeClient::generateFontMetrics(const SkTypefaceProxy& typefaceProxy,
const SkScalerContextRec& rec,
SkPaint::FontMetrics* metrics) {
TRACE_EVENT1("skia", "generateFontMetrics", "rec", TRACE_STR_COPY(rec.dump().c_str()));
SkDebugf("generateFontMetrics: %s\n", rec.dump().c_str());
SkStrikeCache::Dump();
SkDEBUGFAIL("GlyphCacheMiss");
}
void SkStrikeClient::generateMetricsAndImage(const SkTypefaceProxy& typefaceProxy,
const SkScalerContextRec& rec,
SkArenaAlloc* alloc,
SkGlyph* glyph) {
TRACE_EVENT1("skia", "generateMetricsAndImage", "rec", TRACE_STR_COPY(rec.dump().c_str()));
SkDebugf("generateMetricsAndImage: %s\n", rec.dump().c_str());
SkStrikeCache::Dump();
SkDEBUGFAIL("GlyphCacheMiss");
}
void SkStrikeClient::generatePath(const SkTypefaceProxy& typefaceProxy,
const SkScalerContextRec& rec,
SkGlyphID glyphID,
SkPath* path) {
TRACE_EVENT1("skia", "generateMetricsAndImage", "rec", TRACE_STR_COPY(rec.dump().c_str()));
SkDebugf("generatePath: %s\n", rec.dump().c_str());
SkStrikeCache::Dump();
SkDEBUGFAIL("GlyphCacheMiss");
}