| /* | |
| * Copyright 2006 The Android Open Source Project | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/core/SkStrike.h" | |
| #include "include/core/SkDrawable.h" | |
| #include "include/core/SkFontStyle.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPath.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkTraceMemoryDump.h" | |
| #include "include/core/SkTypeface.h" | |
| #include "include/private/base/SkDebug.h" | |
| #include "include/private/base/SkTFitsIn.h" | |
| #include "src/core/SkGlyph.h" | |
| #include "src/core/SkMask.h" | |
| #include "src/core/SkReadBuffer.h" | |
| #include "src/core/SkScalerContext.h" | |
| #include "src/core/SkStrikeCache.h" | |
| #include "src/core/SkWriteBuffer.h" | |
| #include "src/text/StrikeForGPU.h" | |
| #include <cctype> | |
| #include <new> | |
| #include <optional> | |
| #include <utility> | |
| using namespace skglyph; | |
| static SkFontMetrics use_or_generate_metrics( | |
| const SkFontMetrics* metrics, SkScalerContext* context) { | |
| SkFontMetrics answer; | |
| if (metrics) { | |
| answer = *metrics; | |
| } else { | |
| context->getFontMetrics(&answer); | |
| } | |
| return answer; | |
| } | |
| SkStrike::SkStrike(SkStrikeCache* strikeCache, | |
| const SkStrikeSpec& strikeSpec, | |
| std::unique_ptr<SkScalerContext> scaler, | |
| const SkFontMetrics* metrics, | |
| std::unique_ptr<SkStrikePinner> pinner) | |
| : fFontMetrics{use_or_generate_metrics(metrics, scaler.get())} | |
| , fRoundingSpec{scaler->isSubpixel(), | |
| scaler->computeAxisAlignmentForHText()} | |
| , fStrikeSpec{strikeSpec} | |
| , fStrikeCache{strikeCache} | |
| , fScalerContext{std::move(scaler)} | |
| , fPinner{std::move(pinner)} { | |
| SkASSERT(fScalerContext != nullptr); | |
| } | |
| class SK_SCOPED_CAPABILITY SkStrike::Monitor { | |
| public: | |
| Monitor(SkStrike* strike) SK_ACQUIRE(strike->fStrikeLock) | |
| : fStrike{strike} { | |
| fStrike->lock(); | |
| } | |
| ~Monitor() SK_RELEASE_CAPABILITY() { | |
| fStrike->unlock(); | |
| } | |
| private: | |
| SkStrike* const fStrike; | |
| }; | |
| void SkStrike::lock() { | |
| fStrikeLock.acquire(); | |
| fMemoryIncrease = 0; | |
| } | |
| void SkStrike::unlock() { | |
| const size_t memoryIncrease = fMemoryIncrease; | |
| fStrikeLock.release(); | |
| this->updateMemoryUsage(memoryIncrease); | |
| } | |
| void | |
| SkStrike::FlattenGlyphsByType(SkWriteBuffer& buffer, | |
| SkSpan<SkGlyph> images, | |
| SkSpan<SkGlyph> paths, | |
| SkSpan<SkGlyph> drawables) { | |
| SkASSERT_RELEASE(SkTFitsIn<int>(images.size()) && | |
| SkTFitsIn<int>(paths.size()) && | |
| SkTFitsIn<int>(drawables.size())); | |
| buffer.writeInt(images.size()); | |
| for (SkGlyph& glyph : images) { | |
| SkASSERT(SkMask::IsValidFormat(glyph.maskFormat())); | |
| glyph.flattenMetrics(buffer); | |
| glyph.flattenImage(buffer); | |
| } | |
| buffer.writeInt(paths.size()); | |
| for (SkGlyph& glyph : paths) { | |
| SkASSERT(SkMask::IsValidFormat(glyph.maskFormat())); | |
| glyph.flattenMetrics(buffer); | |
| glyph.flattenPath(buffer); | |
| } | |
| buffer.writeInt(drawables.size()); | |
| for (SkGlyph& glyph : drawables) { | |
| SkASSERT(SkMask::IsValidFormat(glyph.maskFormat())); | |
| glyph.flattenMetrics(buffer); | |
| glyph.flattenDrawable(buffer); | |
| } | |
| } | |
| bool SkStrike::mergeFromBuffer(SkReadBuffer& buffer) { | |
| // Read glyphs with images for the current strike. | |
| const int imagesCount = buffer.readInt(); | |
| if (imagesCount == 0 && !buffer.isValid()) { | |
| return false; | |
| } | |
| { | |
| Monitor m{this}; | |
| for (int curImage = 0; curImage < imagesCount; ++curImage) { | |
| if (!this->mergeGlyphAndImageFromBuffer(buffer)) { | |
| return false; | |
| } | |
| } | |
| } | |
| // Read glyphs with paths for the current strike. | |
| const int pathsCount = buffer.readInt(); | |
| if (pathsCount == 0 && !buffer.isValid()) { | |
| return false; | |
| } | |
| { | |
| Monitor m{this}; | |
| for (int curPath = 0; curPath < pathsCount; ++curPath) { | |
| if (!this->mergeGlyphAndPathFromBuffer(buffer)) { | |
| return false; | |
| } | |
| } | |
| } | |
| // Read glyphs with drawables for the current strike. | |
| const int drawablesCount = buffer.readInt(); | |
| if (drawablesCount == 0 && !buffer.isValid()) { | |
| return false; | |
| } | |
| { | |
| Monitor m{this}; | |
| for (int curDrawable = 0; curDrawable < drawablesCount; ++curDrawable) { | |
| if (!this->mergeGlyphAndDrawableFromBuffer(buffer)) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| SkGlyph* SkStrike::mergeGlyphAndImage(SkPackedGlyphID toID, const SkGlyph& fromGlyph) { | |
| Monitor m{this}; | |
| // TODO(herb): remove finding the glyph when setting the metrics and image are separated | |
| SkGlyphDigest* digest = fDigestForPackedGlyphID.find(toID); | |
| if (digest != nullptr) { | |
| SkGlyph* glyph = fGlyphForIndex[digest->index()]; | |
| if (fromGlyph.setImageHasBeenCalled()) { | |
| if (glyph->setImageHasBeenCalled()) { | |
| // Should never set an image on a glyph which already has an image. | |
| SkDEBUGFAIL("Re-adding image to existing glyph. This should not happen."); | |
| } | |
| // TODO: assert that any metrics on fromGlyph are the same. | |
| fMemoryIncrease += glyph->setMetricsAndImage(&fAlloc, fromGlyph); | |
| } | |
| return glyph; | |
| } else { | |
| SkGlyph* glyph = fAlloc.make<SkGlyph>(toID); | |
| fMemoryIncrease += glyph->setMetricsAndImage(&fAlloc, fromGlyph) + sizeof(SkGlyph); | |
| (void)this->addGlyphAndDigest(glyph); | |
| return glyph; | |
| } | |
| } | |
| const SkPath* SkStrike::mergePath(SkGlyph* glyph, const SkPath* path, bool hairline, bool modified) { | |
| Monitor m{this}; | |
| if (glyph->setPathHasBeenCalled()) { | |
| SkDEBUGFAIL("Re-adding path to existing glyph. This should not happen."); | |
| } | |
| if (glyph->setPath(&fAlloc, path, hairline, modified)) { | |
| fMemoryIncrease += glyph->path()->approximateBytesUsed(); | |
| } | |
| return glyph->path(); | |
| } | |
| const SkDrawable* SkStrike::mergeDrawable(SkGlyph* glyph, sk_sp<SkDrawable> drawable) { | |
| Monitor m{this}; | |
| if (glyph->setDrawableHasBeenCalled()) { | |
| SkDEBUGFAIL("Re-adding drawable to existing glyph. This should not happen."); | |
| } | |
| if (glyph->setDrawable(&fAlloc, std::move(drawable))) { | |
| fMemoryIncrease += glyph->drawable()->approximateBytesUsed(); | |
| SkASSERT(fMemoryIncrease > 0); | |
| } | |
| return glyph->drawable(); | |
| } | |
| void SkStrike::findIntercepts(const SkScalar bounds[2], SkScalar scale, SkScalar xPos, | |
| SkGlyph* glyph, SkScalar* array, int* count) { | |
| SkAutoMutexExclusive lock{fStrikeLock}; | |
| glyph->ensureIntercepts(bounds, scale, xPos, array, count, &fAlloc); | |
| } | |
| SkSpan<const SkGlyph*> SkStrike::metrics( | |
| SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) { | |
| Monitor m{this}; | |
| return this->internalPrepare(glyphIDs, kMetricsOnly, results); | |
| } | |
| SkSpan<const SkGlyph*> SkStrike::preparePaths( | |
| SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) { | |
| Monitor m{this}; | |
| return this->internalPrepare(glyphIDs, kMetricsAndPath, results); | |
| } | |
| SkSpan<const SkGlyph*> SkStrike::prepareImages( | |
| SkSpan<const SkPackedGlyphID> glyphIDs, const SkGlyph* results[]) { | |
| const SkGlyph** cursor = results; | |
| Monitor m{this}; | |
| for (auto glyphID : glyphIDs) { | |
| SkGlyph* glyph = this->glyph(glyphID); | |
| this->prepareForImage(glyph); | |
| *cursor++ = glyph; | |
| } | |
| return {results, glyphIDs.size()}; | |
| } | |
| SkSpan<const SkGlyph*> SkStrike::prepareDrawables( | |
| SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) { | |
| const SkGlyph** cursor = results; | |
| { | |
| Monitor m{this}; | |
| for (auto glyphID : glyphIDs) { | |
| SkGlyph* glyph = this->glyph(SkPackedGlyphID{glyphID}); | |
| this->prepareForDrawable(glyph); | |
| *cursor++ = glyph; | |
| } | |
| } | |
| return {results, glyphIDs.size()}; | |
| } | |
| void SkStrike::glyphIDsToPaths(SkSpan<sktext::IDOrPath> idsOrPaths) { | |
| Monitor m{this}; | |
| for (sktext::IDOrPath& idOrPath : idsOrPaths) { | |
| SkGlyph* glyph = this->glyph(SkPackedGlyphID{idOrPath.fGlyphID}); | |
| this->prepareForPath(glyph); | |
| new (&idOrPath.fPath) SkPath{*glyph->path()}; | |
| } | |
| } | |
| void SkStrike::glyphIDsToDrawables(SkSpan<sktext::IDOrDrawable> idsOrDrawables) { | |
| Monitor m{this}; | |
| for (sktext::IDOrDrawable& idOrDrawable : idsOrDrawables) { | |
| SkGlyph* glyph = this->glyph(SkPackedGlyphID{idOrDrawable.fGlyphID}); | |
| this->prepareForDrawable(glyph); | |
| SkASSERT(glyph->drawable() != nullptr); | |
| idOrDrawable.fDrawable = glyph->drawable(); | |
| } | |
| } | |
| void SkStrike::dump() const { | |
| SkAutoMutexExclusive lock{fStrikeLock}; | |
| const SkTypeface* face = fScalerContext->getTypeface(); | |
| const SkScalerContextRec& rec = fScalerContext->getRec(); | |
| SkString name; | |
| face->getFamilyName(&name); | |
| SkString msg; | |
| SkFontStyle style = face->fontStyle(); | |
| msg.printf("cache typeface:%x %25s:(%d,%d,%d)\n %s glyphs:%3d", | |
| face->uniqueID(), name.c_str(), style.weight(), style.width(), style.slant(), | |
| rec.dump().c_str(), fDigestForPackedGlyphID.count()); | |
| SkDebugf("%s\n", msg.c_str()); | |
| } | |
| void SkStrike::dumpMemoryStatistics(SkTraceMemoryDump* dump) const { | |
| SkAutoMutexExclusive lock{fStrikeLock}; | |
| const SkTypeface* face = fScalerContext->getTypeface(); | |
| const SkScalerContextRec& rec = fScalerContext->getRec(); | |
| SkString fontName; | |
| face->getFamilyName(&fontName); | |
| // Replace all special characters with '_'. | |
| for (size_t index = 0; index < fontName.size(); ++index) { | |
| if (!std::isalnum(fontName[index])) { | |
| fontName[index] = '_'; | |
| } | |
| } | |
| SkString dumpName = SkStringPrintf("%s/%s_%u/%p", | |
| SkStrikeCache::kGlyphCacheDumpName, | |
| fontName.c_str(), | |
| rec.fTypefaceID, | |
| this); | |
| dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", fMemoryUsed); | |
| dump->dumpNumericValue(dumpName.c_str(), | |
| "glyph_count", "objects", | |
| fDigestForPackedGlyphID.count()); | |
| dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); | |
| } | |
| SkGlyph* SkStrike::glyph(SkGlyphDigest digest) { | |
| return fGlyphForIndex[digest.index()]; | |
| } | |
| SkGlyph* SkStrike::glyph(SkPackedGlyphID packedGlyphID) { | |
| SkGlyphDigest digest = this->digestFor(kDirectMask, packedGlyphID); | |
| return this->glyph(digest); | |
| } | |
| SkGlyphDigest SkStrike::digestFor(ActionType actionType, SkPackedGlyphID packedGlyphID) { | |
| SkGlyphDigest* digestPtr = fDigestForPackedGlyphID.find(packedGlyphID); | |
| if (digestPtr != nullptr && digestPtr->actionFor(actionType) != GlyphAction::kUnset) { | |
| return *digestPtr; | |
| } | |
| SkGlyph* glyph; | |
| if (digestPtr != nullptr) { | |
| glyph = fGlyphForIndex[digestPtr->index()]; | |
| } else { | |
| glyph = fAlloc.make<SkGlyph>(fScalerContext->makeGlyph(packedGlyphID, &fAlloc)); | |
| fMemoryIncrease += sizeof(SkGlyph); | |
| digestPtr = this->addGlyphAndDigest(glyph); | |
| } | |
| digestPtr->setActionFor(actionType, glyph, this); | |
| return *digestPtr; | |
| } | |
| SkGlyphDigest* SkStrike::addGlyphAndDigest(SkGlyph* glyph) { | |
| size_t index = fGlyphForIndex.size(); | |
| SkGlyphDigest digest = SkGlyphDigest{index, *glyph}; | |
| SkGlyphDigest* newDigest = fDigestForPackedGlyphID.set(digest); | |
| fGlyphForIndex.push_back(glyph); | |
| return newDigest; | |
| } | |
| bool SkStrike::prepareForImage(SkGlyph* glyph) { | |
| if (glyph->setImage(&fAlloc, fScalerContext.get())) { | |
| fMemoryIncrease += glyph->imageSize(); | |
| } | |
| return glyph->image() != nullptr; | |
| } | |
| bool SkStrike::prepareForPath(SkGlyph* glyph) { | |
| if (glyph->setPath(&fAlloc, fScalerContext.get())) { | |
| fMemoryIncrease += glyph->path()->approximateBytesUsed(); | |
| } | |
| return glyph->path() !=nullptr; | |
| } | |
| bool SkStrike::prepareForDrawable(SkGlyph* glyph) { | |
| if (glyph->setDrawable(&fAlloc, fScalerContext.get())) { | |
| size_t increase = glyph->drawable()->approximateBytesUsed(); | |
| SkASSERT(increase > 0); | |
| fMemoryIncrease += increase; | |
| } | |
| return glyph->drawable() != nullptr; | |
| } | |
| SkGlyph* SkStrike::mergeGlyphFromBuffer(SkReadBuffer& buffer) { | |
| SkASSERT(buffer.isValid()); | |
| std::optional<SkGlyph> prototypeGlyph = SkGlyph::MakeFromBuffer(buffer); | |
| if (!buffer.validate(prototypeGlyph.has_value())) { | |
| return nullptr; | |
| } | |
| // Check if this glyph has already been seen. | |
| SkGlyphDigest* digestPtr = fDigestForPackedGlyphID.find(prototypeGlyph->getPackedID()); | |
| if (digestPtr != nullptr) { | |
| return fGlyphForIndex[digestPtr->index()]; | |
| } | |
| // This is the first time. Allocate a new glyph. | |
| SkGlyph* glyph = fAlloc.make<SkGlyph>(prototypeGlyph.value()); | |
| fMemoryIncrease += sizeof(SkGlyph); | |
| this->addGlyphAndDigest(glyph); | |
| return glyph; | |
| } | |
| bool SkStrike::mergeGlyphAndImageFromBuffer(SkReadBuffer& buffer) { | |
| SkASSERT(buffer.isValid()); | |
| SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer); | |
| if (!buffer.validate(glyph != nullptr)) { | |
| return false; | |
| } | |
| fMemoryIncrease += glyph->addImageFromBuffer(buffer, &fAlloc); | |
| return buffer.isValid(); | |
| } | |
| bool SkStrike::mergeGlyphAndPathFromBuffer(SkReadBuffer& buffer) { | |
| SkASSERT(buffer.isValid()); | |
| SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer); | |
| if (!buffer.validate(glyph != nullptr)) { | |
| return false; | |
| } | |
| fMemoryIncrease += glyph->addPathFromBuffer(buffer, &fAlloc); | |
| return buffer.isValid(); | |
| } | |
| bool SkStrike::mergeGlyphAndDrawableFromBuffer(SkReadBuffer& buffer) { | |
| SkASSERT(buffer.isValid()); | |
| SkGlyph* glyph = this->mergeGlyphFromBuffer(buffer); | |
| if (!buffer.validate(glyph != nullptr)) { | |
| return false; | |
| } | |
| fMemoryIncrease += glyph->addDrawableFromBuffer(buffer, &fAlloc); | |
| return buffer.isValid(); | |
| } | |
| SkSpan<const SkGlyph*> SkStrike::internalPrepare( | |
| SkSpan<const SkGlyphID> glyphIDs, PathDetail pathDetail, const SkGlyph** results) { | |
| const SkGlyph** cursor = results; | |
| for (auto glyphID : glyphIDs) { | |
| SkGlyph* glyph = this->glyph(SkPackedGlyphID{glyphID}); | |
| if (pathDetail == kMetricsAndPath) { | |
| this->prepareForPath(glyph); | |
| } | |
| *cursor++ = glyph; | |
| } | |
| return {results, glyphIDs.size()}; | |
| } | |
| void SkStrike::updateMemoryUsage(size_t increase) { | |
| if (increase > 0) { | |
| // fRemoved and the cache's total memory are managed under the cache's lock. This allows | |
| // them to be accessed under LRU operation. | |
| SkAutoMutexExclusive lock{fStrikeCache->fLock}; | |
| fMemoryUsed += increase; | |
| if (!fRemoved) { | |
| fStrikeCache->fTotalMemoryUsed += increase; | |
| } | |
| } | |
| } |