aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Mike Reed <reed@google.com>2017-07-11 10:27:40 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-07-11 14:49:31 +0000
commitb99beddc7a0f6003d6dd88119e7f2734fc508322 (patch)
tree0b213528a3741e155c48c74a881af5c4c52b4a10
parent327290fcafab2e702c839da8f3b7b29428294ea2 (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.h17
-rw-r--r--src/core/SkPaint.cpp11
-rw-r--r--src/core/SkTextBlob.cpp259
-rw-r--r--tests/TextBlobTest.cpp79
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());
+ }
+}