Make fPathData private and add API for paths

The new API eliminates all need to access the path inner workings.

There are some uses of the cast (SkGlyph*) these are to facilitate
the larger change this is a part of. The will be eliminated when all
is done.

Some of the code has been changed to use strike->glyph(id) and SkGlyph*
to help with the flow of the code.

Change-Id: Id8dc84076f56e1e39450367a0440d15954dbdc71
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/220523
Commit-Queue: Herb Derby <herb@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
This commit is contained in:
Herb Derby 2019-06-12 11:14:50 -04:00 committed by Skia Commit-Bot
parent 1127c0b273
commit f0e7581d94
15 changed files with 170 additions and 174 deletions

View File

@ -460,6 +460,12 @@ public:
*/
int getVerbs(uint8_t verbs[], int max) const;
/** Returns the approximate byte size of the SkPath in memory.
@return approximate size
*/
size_t approximateBytesUsed() const;
/** Exchanges the verb array, SkPoint array, weights, and SkPath::FillType with other.
Cached state is also exchanged. swap() internally exchanges pointers, so
it is lightweight and does not allocate memory.

View File

@ -355,14 +355,14 @@ void SkFont::getPaths(const SkGlyphID glyphs[], int count,
void (*proc)(const SkPath*, const SkMatrix&, void*), void* ctx) const {
SkFont font(*this);
SkScalar scale = font.setupForAsPaths(nullptr);
const SkMatrix mx = SkMatrix::MakeScale(scale, scale);
const SkMatrix mx = SkMatrix::MakeScale(scale);
SkStrikeSpec strikeSpec = SkStrikeSpec::MakeCanonicalized(font);
auto exclusive = strikeSpec.findOrCreateExclusiveStrike();
auto cache = exclusive.get();
for (int i = 0; i < count; ++i) {
proc(cache->findPath(cache->getGlyphIDMetrics(glyphs[i])), mx, ctx);
proc(cache->preparePath(cache->glyph(glyphs[i])), mx, ctx);
}
}

View File

@ -117,18 +117,47 @@ size_t SkGlyph::copyImageData(const SkGlyph& from, SkArenaAlloc* alloc) {
return 0u;
}
SkPath* SkGlyph::addPath(SkScalerContext* scalerContext, SkArenaAlloc* alloc) {
if (!this->isEmpty()) {
if (fPathData == nullptr) {
fPathData = alloc->make<SkGlyph::PathData>();
if (scalerContext->getPath(this->getPackedID(), &fPathData->fPath)) {
fPathData->fPath.updateBoundsCache();
fPathData->fPath.getGenerationID();
fPathData->fHasPath = true;
}
}
void SkGlyph::installPath(SkArenaAlloc* alloc, const SkPath* path) {
SkASSERT(fPathData == nullptr);
SkASSERT(!this->setPathHasBeenCalled());
fPathData = alloc->make<SkGlyph::PathData>();
if (path != nullptr) {
fPathData->fPath = *path;
fPathData->fPath.updateBoundsCache();
fPathData->fPath.getGenerationID();
fPathData->fHasPath = true;
}
return this->path();
}
bool SkGlyph::setPath(SkArenaAlloc* alloc, SkScalerContext* scalerContext) {
if (!this->setPathHasBeenCalled()) {
SkPath path;
if (scalerContext->getPath(this->getPackedID(), &path)) {
this->installPath(alloc, &path);
} else {
this->installPath(alloc, nullptr);
}
return this->path() != nullptr;
}
return false;
}
bool SkGlyph::setPath(SkArenaAlloc* alloc, const SkPath* path) {
if (!this->setPathHasBeenCalled()) {
this->installPath(alloc, path);
return this->path() != nullptr;
}
return false;
}
const SkPath* SkGlyph::path() const {
// setPath must have been called previously.
SkASSERT(this->setPathHasBeenCalled());
if (fPathData->fHasPath) {
return &fPathData->fPath;
}
return nullptr;
}
static std::tuple<SkScalar, SkScalar> calculate_path_gap(
@ -136,7 +165,7 @@ static std::tuple<SkScalar, SkScalar> calculate_path_gap(
// Left and Right of an ever expanding gap around the path.
SkScalar left = SK_ScalarMax,
right = SK_ScalarMin;
right = SK_ScalarMin;
auto expandGap = [&left, &right](SkScalar v) {
left = SkTMin(left, v);
right = SkTMax(right, v);

View File

@ -153,21 +153,26 @@ public:
SkMask mask(SkPoint position) const;
SkPath* addPath(SkScalerContext*, SkArenaAlloc*);
// If we haven't already tried to associate a path to this glyph
// (i.e. setPathHasBeenCalled() returns false), then use the
// SkScalerContext or SkPath argument to try to do so. N.B. this
// may still result in no path being associated with this glyph,
// e.g. if you pass a null SkPath or the typeface is bitmap-only.
//
// This setPath() call is sticky... once you call it, the glyph
// stays in its state permanently, ignoring any future calls.
//
// Returns true if this is the first time you called setPath()
// and there actually is a path; call path() to get it.
bool setPath(SkArenaAlloc* alloc, SkScalerContext* scalerContext);
bool setPath(SkArenaAlloc* alloc, const SkPath* path);
SkPath* path() const {
return fPathData != nullptr && fPathData->fHasPath ? &fPathData->fPath : nullptr;
}
// Returns true if that path has been set.
bool setPathHasBeenCalled() const { return fPathData != nullptr; }
bool hasPath() const {
// Need to have called getMetrics before calling findPath.
SkASSERT(fMaskFormat != MASK_FORMAT_UNKNOWN);
// Find path must have been called to use this call.
SkASSERT(fPathData != nullptr);
return fPathData != nullptr && fPathData->fHasPath;
}
// Return a pointer to the path if it exists, otherwise return nullptr. Only works if the
// path was previously set.
const SkPath* path() const;
int maxDimension() const {
// width and height are only defined if a metrics call was made.
@ -190,11 +195,6 @@ public:
void* fImage = nullptr;
// Path data has tricky state. If the glyph isEmpty, then fPathData should always be nullptr,
// else if fPathData is not null, then a path has been requested. The fPath field of fPathData
// may still be null after the request meaning that there is no path for this glyph.
PathData* fPathData = nullptr;
// The width and height of the glyph mask.
uint16_t fWidth = 0,
fHeight = 0;
@ -239,6 +239,14 @@ private:
bool fHasPath{false};
};
// path == nullptr indicates there is no path.
void installPath(SkArenaAlloc* alloc, const SkPath* path);
// Path data has tricky state. If the glyph isEmpty, then fPathData should always be nullptr,
// else if fPathData is not null, then a path has been requested. The fPath field of fPathData
// may still be null after the request meaning that there is no path for this glyph.
PathData* fPathData = nullptr;
// The advance for this glyph.
float fAdvanceX = 0,
fAdvanceY = 0;

View File

@ -167,16 +167,16 @@ void SkGlyphRunListPainter::drawForBitmapDevice(
if (check_glyph_position(position)
&& !glyph.isEmpty()
&& glyph.fMaskFormat != SkMask::kARGB32_Format
&& glyph.hasPath())
&& glyph.path() != nullptr)
{
// Only draw a path if it exists, and this is not a color glyph.
pathsAndPositions.push_back(SkPathPos{glyph.path(), position});
} else {
// TODO: this is here to have chrome layout tests pass. Remove this when
// fallback for CPU works.
strike->generatePath(glyph);
if (check_glyph_position(position) && !glyph.isEmpty() && glyph.hasPath()) {
pathsAndPositions.push_back(SkPathPos{glyph.path(), position});
const SkPath* path = strike->preparePath((SkGlyph*) &glyph);
if (check_glyph_position(position) && !glyph.isEmpty() && path != nullptr) {
pathsAndPositions.push_back(SkPathPos{path, position});
}
}
}
@ -394,7 +394,8 @@ void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunLi
&& glyph.maxDimension() <= SkStrikeCommon::kSkSideTooBigForAtlas) {
// SDF mask will work.
fGlyphPos[glyphsWithMaskCount++] = glyphPos;
} else if (glyph.fMaskFormat != SkMask::kARGB32_Format && glyph.hasPath()) {
} else if (glyph.fMaskFormat != SkMask::kARGB32_Format
&& glyph.path() != nullptr) {
// If not color but too big, use a path.
fPaths.push_back(glyphPos);
} else {
@ -455,7 +456,8 @@ void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunLi
SkPoint position = glyphPos.position;
if (glyph.isEmpty()) {
// do nothing
} else if (glyph.fMaskFormat != SkMask::kARGB32_Format && glyph.hasPath()) {
} else if (glyph.fMaskFormat != SkMask::kARGB32_Format
&& glyph.path() != nullptr) {
// Place paths in fGlyphPos
fGlyphPos[glyphsWithPathCount++] = glyphPos;
} else {
@ -512,7 +514,8 @@ void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunLi
if (glyph.maxDimension() <= SkStrikeCommon::kSkSideTooBigForAtlas) {
fGlyphPos[glyphsWithMaskCount++] = glyphPos;
} else if (glyph.fMaskFormat != SkMask::kARGB32_Format && glyph.hasPath()) {
} else if (glyph.fMaskFormat != SkMask::kARGB32_Format
&& glyph.path() != nullptr) {
fPaths.push_back(glyphPos);
} else {
addFallback(glyph, origin + glyphRun.positions()[glyphPos.index]);

View File

@ -700,6 +700,17 @@ int SkPath::getVerbs(uint8_t dst[], int max) const {
return fPathRef->countVerbs();
}
size_t SkPath::approximateBytesUsed() const {
size_t size = sizeof (SkPath);
if (fPathRef != nullptr) {
size += fPathRef->countPoints() * sizeof(SkPoint)
+ fPathRef->countVerbs()
+ fPathRef->countWeights() * sizeof(SkScalar);
}
return size;
}
bool SkPath::getLastPt(SkPoint* lastPt) const {
SkDEBUGCODE(this->validate();)

View File

@ -165,25 +165,6 @@ private:
// Paths use a SkWriter32 which requires 4 byte alignment.
static const size_t kPathAlignment = 4u;
bool read_path(Deserializer* deserializer, SkGlyph* glyph, SkStrike* cache) {
uint64_t pathSize = 0u;
if (!deserializer->read<uint64_t>(&pathSize)) return false;
if (pathSize == 0u) {
cache->initializePath(glyph, nullptr, 0u);
return true;
}
auto* path = deserializer->read(pathSize, kPathAlignment);
if (!path) return false;
// Don't overwrite the path if we already have one. We could have used a fallback if the
// glyph was missing earlier.
if (glyph->fPathData != nullptr) return true;
return cache->initializePath(glyph, path, pathSize);
}
size_t SkDescriptorMapOperators::operator()(const SkDescriptor* key) const {
return key->getChecksum();
}
@ -579,20 +560,15 @@ const SkGlyph& SkStrikeServer::SkGlyphCacheState::getGlyphMetrics(
//
// A key reason for no path is the fact that the glyph is a color image or is a bitmap only
// font.
void SkStrikeServer::SkGlyphCacheState::generatePath(const SkGlyph& glyph) {
const SkPath* SkStrikeServer::SkGlyphCacheState::preparePath(SkGlyph* glyph) {
// Check to see if we have processed this glyph for a path before.
if (glyph.fPathData == nullptr) {
// Never checked for a path before. Add the path now.
auto path = const_cast<SkGlyph&>(glyph).addPath(fContext.get(), &fAlloc);
if (path != nullptr) {
// A path was added make sure to send it to the GPU.
fCachedGlyphPaths.add(glyph.getPackedID());
fPendingGlyphPaths.push_back(glyph.getPackedID());
}
if (glyph->setPath(&fAlloc, fContext.get())) {
// A path was added make sure to send it to the GPU.
fCachedGlyphPaths.add(glyph->getPackedID());
fPendingGlyphPaths.push_back(glyph->getPackedID());
}
return glyph->path();
}
void SkStrikeServer::SkGlyphCacheState::writeGlyphPath(const SkPackedGlyphID& glyphID,
@ -644,18 +620,13 @@ SkSpan<const SkGlyphPos> SkStrikeServer::SkGlyphCacheState::prepareForDrawing(
// The glyph is too big for the atlas, but it is not color, so it is handled with a
// path.
if (glyphPtr->fPathData == nullptr) {
// Never checked for a path before. Add the path now.
const_cast<SkGlyph&>(*glyphPtr).addPath(fContext.get(), &fAlloc);
if (glyphPtr->setPath(&fAlloc, fContext.get())) {
// Always send the path data, even if its not available, to make sure empty
// paths are not incorrectly assumed to be cache misses.
fCachedGlyphPaths.add(glyphPtr->getPackedID());
fPendingGlyphPaths.push_back(glyphPtr->getPackedID());
}
} else {
// This will be handled by the fallback strike.
SkASSERT(glyphPtr->maxDimension() > maxDimension
&& glyphPtr->fMaskFormat == SkMask::kARGB32_Format);
@ -821,15 +792,19 @@ bool SkStrikeClient::readStrikeData(const volatile void* memory, size_t memorySi
SkGlyph* allocatedGlyph = strike->getRawGlyphByID(glyph->getPackedID());
// Update the glyph unless it's already got a path (from fallback),
// preserving any image that might be present.
if (allocatedGlyph->fPathData == nullptr) {
auto* glyphImage = allocatedGlyph->fImage;
*allocatedGlyph = *glyph;
allocatedGlyph->fImage = glyphImage;
SkPath* pathPtr = nullptr;
SkPath path;
uint64_t pathSize = 0u;
if (!deserializer.read<uint64_t>(&pathSize)) READ_FAILURE
if (pathSize > 0) {
auto* pathData = deserializer.read(pathSize, kPathAlignment);
if (!pathData) READ_FAILURE
if (!path.readFromMemory(const_cast<const void*>(pathData), pathSize)) READ_FAILURE
pathPtr = &path;
}
if (!read_path(&deserializer, allocatedGlyph, strike.get())) READ_FAILURE
strike->preparePath(allocatedGlyph, pathPtr);
}
}

View File

@ -46,7 +46,7 @@ public:
PreparationDetail detail,
SkGlyphPos results[]) override;
void generatePath(const SkGlyph& glyph) override;
const SkPath* preparePath(SkGlyph* glyph) override;
void onAboutToExitScope() override {}

View File

@ -16,12 +16,6 @@
#include "src/core/SkMakeUnique.h"
#include <cctype>
namespace {
size_t compute_path_size(const SkPath& path) {
return sizeof(SkPath) + path.countPoints() * sizeof(SkPoint);
}
} // namespace
SkStrike::SkStrike(
const SkDescriptor& desc,
std::unique_ptr<SkScalerContext> scaler,
@ -71,6 +65,10 @@ SkGlyph* SkStrike::glyph(SkGlyphID glyphID) {
return this->glyph(SkPackedGlyphID{glyphID});
}
SkGlyph* SkStrike::glyphOrNull(SkPackedGlyphID id) const {
return fGlyphMap.findOrNull(id);
}
SkGlyph* SkStrike::getRawGlyphByID(SkPackedGlyphID id) {
return this->uninitializedGlyph(id);
}
@ -90,6 +88,20 @@ const SkGlyph& SkStrike::getGlyphIDMetrics(SkGlyphID glyphID, SkFixed x, SkFixed
return *this->glyph(packedGlyphID);
}
const SkPath* SkStrike::preparePath(SkGlyph* glyph) {
if (glyph->setPath(&fAlloc, fScalerContext.get())) {
fMemoryUsed += glyph->path()->approximateBytesUsed();
}
return glyph->path();
}
const SkPath* SkStrike::preparePath(SkGlyph* glyph, const SkPath* path) {
if (glyph->setPath(&fAlloc, path)) {
fMemoryUsed += glyph->path()->approximateBytesUsed();
}
return glyph->path();
}
const SkDescriptor& SkStrike::getDescriptor() const {
return *fDesc.getDesc();
}
@ -148,46 +160,6 @@ void SkStrike::initializeImage(const volatile void* data, size_t size, SkGlyph*
}
}
const SkPath* SkStrike::findPath(const SkGlyph& glyph) {
if (!glyph.isEmpty()) {
// If the path already exists, return it.
if (glyph.fPathData != nullptr) {
if (glyph.fPathData->fHasPath) {
return &glyph.fPathData->fPath;
}
return nullptr;
}
const_cast<SkGlyph&>(glyph).addPath(fScalerContext.get(), &fAlloc);
if (glyph.fPathData != nullptr) {
fMemoryUsed += compute_path_size(glyph.fPathData->fPath);
}
return glyph.path();
}
return nullptr;
}
bool SkStrike::initializePath(SkGlyph* glyph, const volatile void* data, size_t size) {
SkASSERT(!glyph->fPathData);
if (glyph->fWidth) {
SkGlyph::PathData* pathData = fAlloc.make<SkGlyph::PathData>();
glyph->fPathData = pathData;
if (size == 0u) return true;
auto path = skstd::make_unique<SkPath>();
if (!pathData->fPath.readFromMemory(const_cast<const void*>(data), size)) {
return false;
}
fMemoryUsed += compute_path_size(glyph->fPathData->fPath);
pathData->fHasPath = true;
}
return true;
}
bool SkStrike::belongsToCache(const SkGlyph* glyph) const {
return glyph && fGlyphMap.findOrNull(glyph->getPackedID()) == glyph;
@ -250,7 +222,7 @@ SkSpan<const SkGlyphPos> SkStrike::prepareForDrawing(const SkGlyphID glyphIDs[],
}
} else if (glyph.fMaskFormat != SkMask::kARGB32_Format) {
// The out of atlas glyph is not color so we can draw it using paths.
this->findPath(glyph);
this->preparePath(const_cast<SkGlyph*>(&glyph));
} else {
// This will be handled by the fallback strike.
SkASSERT(glyph.maxDimension() > maxDimension
@ -285,10 +257,6 @@ void SkStrike::dump() const {
SkDebugf("%s\n", msg.c_str());
}
void SkStrike::generatePath(const SkGlyph& glyph) {
if (!glyph.isEmpty()) { this->findPath(glyph); }
}
void SkStrike::onAboutToExitScope() { }
#ifdef SK_DEBUG
@ -299,8 +267,8 @@ void SkStrike::forceValidate() const {
if (glyphPtr->fImage) {
memoryUsed += glyphPtr->computeImageSize();
}
if (glyphPtr->fPathData) {
memoryUsed += compute_path_size(glyphPtr->fPathData->fPath);
if (glyphPtr->setPathHasBeenCalled() && glyphPtr->path() != nullptr) {
memoryUsed += glyphPtr->path()->approximateBytesUsed();
}
});
SkASSERT(fMemoryUsed == memoryUsed);

View File

@ -66,9 +66,18 @@ public:
SkGlyph* glyph(SkPackedGlyphID id);
SkGlyph* glyph(SkGlyphID);
// Return a glyph or nullptr if it does not exits in the strike.
SkGlyph* glyphOrNull(SkPackedGlyphID id) const;
// Return a glyph. Create it if it doesn't exist, but zero the data.
SkGlyph* uninitializedGlyph(SkPackedGlyphID id);
// If the path has never been set, then use the scaler context to add the glyph.
const SkPath* preparePath(SkGlyph*) override;
// If the path has never been set, then add a path to glyph.
const SkPath* preparePath(SkGlyph* glyph, const SkPath* path);
void getAdvances(SkSpan<const SkGlyphID>, SkPoint[]);
/** Returns the number of glyphs for this strike.
@ -93,16 +102,6 @@ public:
void findIntercepts(const SkScalar bounds[2], SkScalar scale, SkScalar xPos,
SkGlyph* , SkScalar* array, int* count);
/** Return the Path associated with the glyph. If it has not been generated this will trigger
that.
*/
const SkPath* findPath(const SkGlyph&);
/** Initializes the path associated with the glyph with |data|. Returns false if
* data is invalid.
*/
bool initializePath(SkGlyph*, const volatile void* data, size_t size);
/** Fallback glyphs used during font remoting if the original glyph can't be found.
*/
bool belongsToCache(const SkGlyph* glyph) const;
@ -131,8 +130,6 @@ public:
const SkGlyph& getGlyphMetrics(SkGlyphID glyphID, SkPoint position) override;
void generatePath(const SkGlyph& glyph) override;
const SkDescriptor& getDescriptor() const override;
SkSpan<const SkGlyphPos> prepareForDrawing(const SkGlyphID glyphIDs[],

View File

@ -46,8 +46,8 @@ public:
glyphIDs, positions, n, maxDimension, detail, results);
}
void generatePath(const SkGlyph& glyph) override {
fStrike.generatePath(glyph);
const SkPath* preparePath(SkGlyph* glyph) override {
return fStrike.preparePath(glyph);
}
const SkDescriptor& getDescriptor() const override {
@ -349,12 +349,11 @@ bool SkStrikeCache::desperationSearchForPath(
// There is also a problem with accounting for cache size with shared path data.
for (Node* node = internalGetHead(); node != nullptr; node = node->fNext) {
if (loose_compare(node->fStrike.getDescriptor(), desc)) {
if (node->fStrike.isGlyphCached(glyphID, 0, 0)) {
SkGlyph* from = node->fStrike.getRawGlyphByID(SkPackedGlyphID(glyphID));
if (from->fPathData != nullptr) {
if (SkGlyph *from = node->fStrike.glyphOrNull(SkPackedGlyphID{glyphID})) {
if (from->setPathHasBeenCalled() && from->path() != nullptr) {
// We can just copy the path out by value here, so no need to worry
// about the lifetime of this desperate-match node.
*path = from->fPathData->fPath;
*path = *from->path();
return true;
}
}

View File

@ -70,8 +70,9 @@ public:
SkGlyphPos results[]) = 0;
virtual const SkGlyph& getGlyphMetrics(SkGlyphID glyphID, SkPoint position) = 0;
// TODO: Deprecated. Do not use. Remove when ARGB fallback for bitmap device paths is working.
virtual void generatePath(const SkGlyph& glyph) = 0;
// If glyph does not have an existing path, then add a path to glyph using a scaler context.
virtual const SkPath* preparePath(SkGlyph* glyph) = 0;
virtual void onAboutToExitScope() = 0;
struct Deleter {

View File

@ -941,11 +941,11 @@ TextInterceptsIter::TextInterceptsIter(const SkGlyphID glyphs[],
bool TextInterceptsIter::next(SkScalar* array, int* count) {
SkASSERT(fGlyphs < fStop);
const SkGlyph& glyph = fCache->getGlyphIDMetrics(*fGlyphs++);
SkGlyph* glyph = fCache->glyph(*fGlyphs++);
fXPos += fPrevAdvance * fScale;
fPrevAdvance = SkFloatToScalar(glyph.advanceX());
if (fCache->findPath(glyph)) {
fCache->findIntercepts(fBounds, fScale, fXPos, const_cast<SkGlyph*>(&glyph), array, count);
fPrevAdvance = glyph->advanceX();
if (fCache->preparePath(glyph) != nullptr) {
fCache->findIntercepts(fBounds, fScale, fXPos, glyph, array, count);
}
return fGlyphs < fStop;
}

View File

@ -778,12 +778,12 @@ static bool needs_new_font(SkPDFFont* font, SkGlyphID gid, SkStrike* cache,
if (fontType == SkAdvancedTypefaceMetrics::kOther_Font) {
return false;
}
const SkGlyph& glyph = cache->getGlyphIDMetrics(gid);
if (glyph.isEmpty()) {
SkGlyph* glyph = cache->glyph(gid);
if (glyph->isEmpty()) {
return false;
}
bool bitmapOnly = nullptr == cache->findPath(glyph);
bool bitmapOnly = nullptr == cache->preparePath(glyph);
bool convertedToType3 = (font->getType() == SkAdvancedTypefaceMetrics::kOther_Font);
return convertedToType3 != bitmapOnly;
}

View File

@ -181,8 +181,8 @@ static SkGlyphID first_nonzero_glyph_for_single_byte_encoding(SkGlyphID gid) {
}
static bool has_outline_glyph(SkGlyphID gid, SkStrike* cache) {
const SkGlyph& glyph = cache->getGlyphIDMetrics(gid);
return glyph.isEmpty() || cache->findPath(glyph);
SkGlyph* glyph = cache->glyph(gid);
return glyph->isEmpty() || cache->preparePath(glyph);
}
SkPDFFont* SkPDFFont::GetFontResource(SkPDFDocument* doc,
@ -569,25 +569,24 @@ static void emit_subset_type3(const SkPDFFont& pdfFont, SkPDFDocument* doc) {
characterName.set("g0");
} else {
characterName.printf("g%X", gID);
const SkGlyph& glyph = cache->getGlyphIDMetrics(gID);
advance = SkFloatToScalar(glyph.advanceX());
glyphBBox = SkIRect::MakeXYWH(glyph.fLeft, glyph.fTop,
glyph.fWidth, glyph.fHeight);
SkGlyph* glyph = cache->glyph(gID);
advance = glyph->advanceX();
glyphBBox = SkIRect::MakeXYWH(glyph->fLeft, glyph->fTop,
glyph->fWidth, glyph->fHeight);
bbox.join(glyphBBox);
const SkPath* path = cache->findPath(glyph);
const SkPath* path = cache->preparePath(glyph);
SkDynamicMemoryWStream content;
if (path && !path->isEmpty()) {
setGlyphWidthAndBoundingBox(SkFloatToScalar(glyph.advanceX()), glyphBBox, &content);
setGlyphWidthAndBoundingBox(glyph->advanceX(), glyphBBox, &content);
SkPDFUtils::EmitPath(*path, SkPaint::kFill_Style, &content);
SkPDFUtils::PaintPath(SkPaint::kFill_Style, path->getFillType(), &content);
} else {
auto pimg = to_image(gID, cache.get());
if (!pimg.fImage) {
setGlyphWidthAndBoundingBox(SkFloatToScalar(glyph.advanceX()), glyphBBox,
&content);
setGlyphWidthAndBoundingBox(glyph->advanceX(), glyphBBox, &content);
} else {
imageGlyphs.emplace_back(gID, SkPDFSerializeImage(pimg.fImage.get(), doc));
SkPDFUtils::AppendScalar(SkFloatToScalar(glyph.advanceX()), &content);
SkPDFUtils::AppendScalar(glyph->advanceX(), &content);
content.writeText(" 0 d0\n");
content.writeDecAsText(pimg.fImage->width());
content.writeText(" 0 0 ");