diff options
author | Mike Reed <reed@google.com> | 2017-07-11 10:27:40 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-07-11 14:49:31 +0000 |
commit | b99beddc7a0f6003d6dd88119e7f2734fc508322 (patch) | |
tree | 0b213528a3741e155c48c74a881af5c4c52b4a10 | |
parent | 327290fcafab2e702c839da8f3b7b29428294ea2 (diff) |
Add serialize + catalog api to SkTextBlob
Bug: skia:6836
Change-Id: I858cf936b015c14f12a41a4912e19bb15de8abaa
Reviewed-on: https://skia-review.googlesource.com/21730
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
-rw-r--r-- | include/core/SkTextBlob.h | 17 | ||||
-rw-r--r-- | src/core/SkPaint.cpp | 11 | ||||
-rw-r--r-- | src/core/SkTextBlob.cpp | 259 | ||||
-rw-r--r-- | tests/TextBlobTest.cpp | 79 |
4 files changed, 258 insertions, 108 deletions
diff --git a/include/core/SkTextBlob.h b/include/core/SkTextBlob.h index 956c6a092d..a4d2dba9a0 100644 --- a/include/core/SkTextBlob.h +++ b/include/core/SkTextBlob.h @@ -17,6 +17,9 @@ class SkReadBuffer; class SkWriteBuffer; +typedef std::function<void(SkTypeface*)> SkTypefaceCataloger; +typedef std::function<sk_sp<SkTypeface>(uint32_t)> SkTypefaceResolver; + /** \class SkTextBlob SkTextBlob combines multiple text runs into an immutable, ref-counted structure. @@ -57,6 +60,20 @@ public: kFull_Positioning = 2 // Point positioning -- two scalars per glyph. }; + /** + * Serialize the typeface into a data blob, storing type uniqueID of each referenced typeface. + * During this process, each time a typeface is encountered, it is passed to the catalog, + * allowing the caller to what typeface IDs will need to be resolved in Deserialize(). + */ + sk_sp<SkData> serialize(const SkTypefaceCataloger&) const; + + /** + * Re-create a text blob previously serialized. Since the serialized form records the uniqueIDs + * of its typefaces, deserialization requires that the caller provide the corresponding + * SkTypefaces for those IDs. + */ + static sk_sp<SkTextBlob> Deserialize(const void* data, size_t size, const SkTypefaceResolver&); + private: friend class SkNVRefCnt<SkTextBlob>; class RunRecord; diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp index dcd29d6100..58f132d6d1 100644 --- a/src/core/SkPaint.cpp +++ b/src/core/SkPaint.cpp @@ -1861,8 +1861,15 @@ static FlatFlags unpack_paint_flags(SkPaint* paint, uint32_t packed) { it if there are not tricky elements like shaders, etc. */ void SkPaint::flatten(SkWriteBuffer& buffer) const { + // If the writer is xprocess, then we force recording our typeface, even if its "default" + // since the other process may have a different notion of default. + SkTypeface* tf = this->getTypeface(); + if (!tf && buffer.isCrossProcess()) { + tf = SkTypeface::GetDefaultTypeface(SkTypeface::kNormal); + } + uint8_t flatFlags = 0; - if (this->getTypeface()) { + if (tf) { flatFlags |= kHasTypeface_FlatFlag; } if (asint(this->getPathEffect()) | @@ -1891,7 +1898,7 @@ void SkPaint::flatten(SkWriteBuffer& buffer) const { // now we're done with ptr and the (pre)reserved space. If we need to write // additional fields, use the buffer directly if (flatFlags & kHasTypeface_FlatFlag) { - buffer.writeTypeface(this->getTypeface()); + buffer.writeTypeface(tf); } if (flatFlags & kHasEffects_FlatFlag) { buffer.writeFlattenable(this->getPathEffect()); diff --git a/src/core/SkTextBlob.cpp b/src/core/SkTextBlob.cpp index 6fc4bea2fb..a6cd744efc 100644 --- a/src/core/SkTextBlob.cpp +++ b/src/core/SkTextBlob.cpp @@ -320,112 +320,6 @@ union PositioningAndExtended { }; } // namespace -void SkTextBlob::flatten(SkWriteBuffer& buffer) const { - buffer.writeRect(fBounds); - - SkPaint runPaint; - SkTextBlobRunIterator it(this); - while (!it.done()) { - SkASSERT(it.glyphCount() > 0); - - buffer.write32(it.glyphCount()); - PositioningAndExtended pe; - pe.intValue = 0; - pe.positioning = it.positioning(); - SkASSERT((int32_t)it.positioning() == pe.intValue); // backwards compat. - - uint32_t textSize = it.textSize(); - pe.extended = textSize > 0; - buffer.write32(pe.intValue); - if (pe.extended) { - buffer.write32(textSize); - } - buffer.writePoint(it.offset()); - // This should go away when switching to SkFont - it.applyFontToPaint(&runPaint); - buffer.writePaint(runPaint); - - buffer.writeByteArray(it.glyphs(), it.glyphCount() * sizeof(uint16_t)); - buffer.writeByteArray(it.pos(), - it.glyphCount() * sizeof(SkScalar) * ScalarsPerGlyph(it.positioning())); - if (pe.extended) { - buffer.writeByteArray(it.clusters(), sizeof(uint32_t) * it.glyphCount()); - buffer.writeByteArray(it.text(), it.textSize()); - } - - it.next(); - } - - // Marker for the last run (0 is not a valid glyph count). - buffer.write32(0); -} - -sk_sp<SkTextBlob> SkTextBlob::MakeFromBuffer(SkReadBuffer& reader) { - const int runCount = reader.isVersionLT(SkReadBuffer::kTextBlobImplicitRunCount_Version) - ? reader.read32() : std::numeric_limits<int>::max(); - if (runCount < 0) { - return nullptr; - } - - SkRect bounds; - reader.readRect(&bounds); - - SkTextBlobBuilder blobBuilder; - for (int i = 0; i < runCount; ++i) { - int glyphCount = reader.read32(); - if (glyphCount == 0 && - !reader.isVersionLT(SkReadBuffer::kTextBlobImplicitRunCount_Version)) { - // End-of-runs marker. - break; - } - - PositioningAndExtended pe; - pe.intValue = reader.read32(); - GlyphPositioning pos = pe.positioning; - if (glyphCount <= 0 || pos > kFull_Positioning) { - return nullptr; - } - uint32_t textSize = pe.extended ? (uint32_t)reader.read32() : 0; - - SkPoint offset; - reader.readPoint(&offset); - SkPaint font; - reader.readPaint(&font); - - const SkTextBlobBuilder::RunBuffer* buf = nullptr; - switch (pos) { - case kDefault_Positioning: - buf = &blobBuilder.allocRunText(font, glyphCount, offset.x(), offset.y(), - textSize, SkString(), &bounds); - break; - case kHorizontal_Positioning: - buf = &blobBuilder.allocRunTextPosH(font, glyphCount, offset.y(), - textSize, SkString(), &bounds); - break; - case kFull_Positioning: - buf = &blobBuilder.allocRunTextPos(font, glyphCount, textSize, SkString(), &bounds); - break; - default: - return nullptr; - } - - if (!reader.readByteArray(buf->glyphs, glyphCount * sizeof(uint16_t)) || - !reader.readByteArray(buf->pos, - glyphCount * sizeof(SkScalar) * ScalarsPerGlyph(pos))) { - return nullptr; - } - - if (pe.extended) { - if (!reader.readByteArray(buf->clusters, glyphCount * sizeof(uint32_t)) || - !reader.readByteArray(buf->utf8text, textSize)) { - return nullptr; - } - } - } - - return blobBuilder.make(); -} - unsigned SkTextBlob::ScalarsPerGlyph(GlyphPositioning pos) { // GlyphPositioning values are directly mapped to scalars-per-glyph. SkASSERT(pos <= 2); @@ -810,3 +704,156 @@ sk_sp<SkTextBlob> SkTextBlobBuilder::make() { return sk_sp<SkTextBlob>(blob); } + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +void SkTextBlob::flatten(SkWriteBuffer& buffer) const { + buffer.writeRect(fBounds); + + SkPaint runPaint; + SkTextBlobRunIterator it(this); + while (!it.done()) { + SkASSERT(it.glyphCount() > 0); + + buffer.write32(it.glyphCount()); + PositioningAndExtended pe; + pe.intValue = 0; + pe.positioning = it.positioning(); + SkASSERT((int32_t)it.positioning() == pe.intValue); // backwards compat. + + uint32_t textSize = it.textSize(); + pe.extended = textSize > 0; + buffer.write32(pe.intValue); + if (pe.extended) { + buffer.write32(textSize); + } + buffer.writePoint(it.offset()); + // This should go away when switching to SkFont + it.applyFontToPaint(&runPaint); + buffer.writePaint(runPaint); + + buffer.writeByteArray(it.glyphs(), it.glyphCount() * sizeof(uint16_t)); + buffer.writeByteArray(it.pos(), + it.glyphCount() * sizeof(SkScalar) * ScalarsPerGlyph(it.positioning())); + if (pe.extended) { + buffer.writeByteArray(it.clusters(), sizeof(uint32_t) * it.glyphCount()); + buffer.writeByteArray(it.text(), it.textSize()); + } + + it.next(); + } + + // Marker for the last run (0 is not a valid glyph count). + buffer.write32(0); +} + +sk_sp<SkTextBlob> SkTextBlob::MakeFromBuffer(SkReadBuffer& reader) { + const int runCount = reader.isVersionLT(SkReadBuffer::kTextBlobImplicitRunCount_Version) + ? reader.read32() : std::numeric_limits<int>::max(); + if (runCount < 0) { + return nullptr; + } + + SkRect bounds; + reader.readRect(&bounds); + + SkTextBlobBuilder blobBuilder; + for (int i = 0; i < runCount; ++i) { + int glyphCount = reader.read32(); + if (glyphCount == 0 && + !reader.isVersionLT(SkReadBuffer::kTextBlobImplicitRunCount_Version)) { + // End-of-runs marker. + break; + } + + PositioningAndExtended pe; + pe.intValue = reader.read32(); + GlyphPositioning pos = pe.positioning; + if (glyphCount <= 0 || pos > kFull_Positioning) { + return nullptr; + } + uint32_t textSize = pe.extended ? (uint32_t)reader.read32() : 0; + + SkPoint offset; + reader.readPoint(&offset); + SkPaint font; + reader.readPaint(&font); + + const SkTextBlobBuilder::RunBuffer* buf = nullptr; + switch (pos) { + case kDefault_Positioning: + buf = &blobBuilder.allocRunText(font, glyphCount, offset.x(), offset.y(), + textSize, SkString(), &bounds); + break; + case kHorizontal_Positioning: + buf = &blobBuilder.allocRunTextPosH(font, glyphCount, offset.y(), + textSize, SkString(), &bounds); + break; + case kFull_Positioning: + buf = &blobBuilder.allocRunTextPos(font, glyphCount, textSize, SkString(), &bounds); + break; + default: + return nullptr; + } + + if (!reader.readByteArray(buf->glyphs, glyphCount * sizeof(uint16_t)) || + !reader.readByteArray(buf->pos, + glyphCount * sizeof(SkScalar) * ScalarsPerGlyph(pos))) { + return nullptr; + } + + if (pe.extended) { + if (!reader.readByteArray(buf->clusters, glyphCount * sizeof(uint32_t)) || + !reader.readByteArray(buf->utf8text, textSize)) { + return nullptr; + } + } + } + + return blobBuilder.make(); +} + +class SkTypefaceCatalogerWriteBuffer : public SkBinaryWriteBuffer { +public: + SkTypefaceCatalogerWriteBuffer(const SkTypefaceCataloger& cataloger) + : SkBinaryWriteBuffer(SkBinaryWriteBuffer::kCrossProcess_Flag) + , fCataloger(cataloger) + {} + + void writeTypeface(SkTypeface* typeface) override { + fCataloger(typeface); + this->write32(typeface ? typeface->uniqueID() : 0); + } + + const SkTypefaceCataloger& fCataloger; +}; + +sk_sp<SkData> SkTextBlob::serialize(const SkTypefaceCataloger& cataloger) const { + SkTypefaceCatalogerWriteBuffer buffer(cataloger); + this->flatten(buffer); + + size_t total = buffer.bytesWritten(); + sk_sp<SkData> data = SkData::MakeUninitialized(total); + buffer.writeToMemory(data->writable_data()); + return data; +} + +class SkTypefaceResolverReadBuffer : public SkReadBuffer { +public: + SkTypefaceResolverReadBuffer(const void* data, size_t size, const SkTypefaceResolver& resolver) + : SkReadBuffer(data, size) + , fResolver(resolver) + {} + + sk_sp<SkTypeface> readTypeface() override { + return fResolver(this->read32()); + } + + const SkTypefaceResolver& fResolver; +}; + +sk_sp<SkTextBlob> SkTextBlob::Deserialize(const void* data, size_t length, + const SkTypefaceResolver& resolver) { + SkTypefaceResolverReadBuffer buffer(data, length, resolver); + return SkTextBlob::MakeFromBuffer(buffer); +} diff --git a/tests/TextBlobTest.cpp b/tests/TextBlobTest.cpp index 38341163ac..238d7340b8 100644 --- a/tests/TextBlobTest.cpp +++ b/tests/TextBlobTest.cpp @@ -383,3 +383,82 @@ DEF_TEST(TextBlob_extended, reporter) { REPORTER_ASSERT(reporter, 0 == strncmp(text2, it.text(), it.textSize())); } } + +/////////////////////////////////////////////////////////////////////////////////////////////////// +#include "SkCanvas.h" +#include "SkSurface.h" +#include "SkTDArray.h" + +static void add_run(SkTextBlobBuilder* builder, const char text[], SkScalar x, SkScalar y, + sk_sp<SkTypeface> tf) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setSubpixelText(true); + paint.setTextSize(16); + paint.setTypeface(tf); + + int glyphCount = paint.textToGlyphs(text, strlen(text), nullptr); + + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + SkTextBlobBuilder::RunBuffer buffer = builder->allocRun(paint, glyphCount, x, y); + + paint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + (void)paint.textToGlyphs(text, strlen(text), buffer.glyphs); +} + +static sk_sp<SkImage> render(const SkTextBlob* blob) { + auto surf = SkSurface::MakeRasterN32Premul(SkScalarRoundToInt(blob->bounds().width()), + SkScalarRoundToInt(blob->bounds().height())); + surf->getCanvas()->clear(SK_ColorWHITE); + surf->getCanvas()->drawTextBlob(blob, -blob->bounds().left(), -blob->bounds().top(), SkPaint()); + return surf->makeImageSnapshot(); +} + +/* + * Build a blob with more than one typeface. + * Draw it into an offscreen, + * then serialize and deserialize, + * Then draw the new instance and assert it draws the same as the original. + */ +DEF_TEST(TextBlob_serialize, reporter) { + SkTextBlobBuilder builder; + + sk_sp<SkTypeface> tf0; + sk_sp<SkTypeface> tf1 = SkTypeface::MakeFromName("Times", SkFontStyle()); + + add_run(&builder, "Hello", 10, 20, tf0); + add_run(&builder, "World", 10, 40, tf1); + sk_sp<SkTextBlob> blob0 = builder.make(); + sk_sp<SkImage> img0 = render(blob0.get()); + + SkTDArray<SkTypeface*> array; + sk_sp<SkData> data = blob0->serialize([&array](SkTypeface* tf) { + if (array.find(tf) < 0) { + *array.append() = tf; + } + }); + REPORTER_ASSERT(reporter, array.count() > 0); + + sk_sp<SkTextBlob> blob1 = SkTextBlob::Deserialize(data->data(), data->size(), + [&array, reporter](uint32_t uniqueID) { + for (int i = 0; i < array.count(); ++i) { + if (array[i]->uniqueID() == uniqueID) { + return sk_ref_sp(array[i]); + } + } + REPORTER_ASSERT(reporter, false); + return sk_sp<SkTypeface>(nullptr); + }); + sk_sp<SkImage> img1 = render(blob1.get()); + + REPORTER_ASSERT(reporter, img0->width() == img1->width()); + REPORTER_ASSERT(reporter, img0->height() == img1->height()); + + sk_sp<SkData> enc0(img0->encode()); + sk_sp<SkData> enc1(img1->encode()); + REPORTER_ASSERT(reporter, enc0->equals(enc1.get())); + if (false) { // in case you want to actually see the images... + SkFILEWStream("textblob_serialize_img0.png").write(enc0->data(), enc0->size()); + SkFILEWStream("textblob_serialize_img1.png").write(enc1->data(), enc1->size()); + } +} |