SkTextBlob: Begin implementing Extended TextBlob API
authorhalcanary <halcanary@google.com>
Tue, 30 Aug 2016 18:58:33 +0000 (11:58 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 30 Aug 2016 18:58:33 +0000 (11:58 -0700)
BUG=skia:5434
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2084533004

Review-Url: https://codereview.chromium.org/2084533004

include/core/SkPicture.h
include/core/SkTextBlob.h
src/core/SkTextBlob.cpp
src/core/SkTextBlobRunIterator.h
tests/TextBlobTest.cpp
tools/SkShaper_harfbuzz.cpp
tools/using_skia_and_harfbuzz.cpp

index 7cfa667..9ba8510 100644 (file)
@@ -213,10 +213,11 @@ private:
     // V45: Add invNormRotation to SkLightingShader.
     // V46: Add drawTextRSXform
     // V47: Add occluder rect to SkBlurMaskFilter
+    // V48: Read and write extended SkTextBlobs.
 
     // Only SKPs within the min/current picture version range (inclusive) can be read.
     static const uint32_t     MIN_PICTURE_VERSION = 35;     // Produced by Chrome M39.
-    static const uint32_t CURRENT_PICTURE_VERSION = 47;
+    static const uint32_t CURRENT_PICTURE_VERSION = 48;
 
     static_assert(MIN_PICTURE_VERSION <= 41,
                   "Remove kFontFileName and related code from SkFontDescriptor.cpp.");
index 1addb6f..01263af 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "../private/SkTemplates.h"
 #include "SkPaint.h"
+#include "SkString.h"
 #include "SkRefCnt.h"
 
 class SkReadBuffer;
@@ -49,7 +50,7 @@ public:
         return MakeFromBuffer(buffer).release();
     }
 
-    enum GlyphPositioning {
+    enum GlyphPositioning : uint8_t {
         kDefault_Positioning      = 0, // Default glyph advances -- zero scalars per glyph.
         kHorizontal_Positioning   = 1, // Horizontal positioning -- one scalar per glyph.
         kFull_Positioning         = 2  // Point positioning -- two scalars per glyph.
@@ -112,11 +113,29 @@ public:
     /**
      *  Glyph and position buffers associated with a run.
      *
-     *  A run is a sequence of glyphs sharing the same font metrics and positioning mode.
+     *  A run is a sequence of glyphs sharing the same font metrics
+     *  and positioning mode.
+     *
+     *  If textByteCount is 0, utf8text and clusters will be NULL (no
+     *  character information will be associated with the glyphs).
+     *
+     *  utf8text will point to a buffer of size textByteCount bytes.
+     *
+     *  clusters (if not NULL) will point to an array of size count.
+     *  For each glyph, give the byte-offset into the text for the
+     *  first byte in the first character in that glyph's cluster.
+     *  Each value in the array should be an integer less than
+     *  textByteCount.  Values in the array should either be
+     *  monotonically increasing (left-to-right text) or monotonically
+     *  decreasing (right-to-left text).  This definiton is conviently
+     *  the same as used by Harfbuzz's hb_glyph_info_t::cluster field,
+     *  except that Harfbuzz interleaves glyphs and clusters.
      */
     struct RunBuffer {
         SkGlyphID* glyphs;
         SkScalar* pos;
+        char* utf8text;
+        uint32_t* clusters;
     };
 
     /**
@@ -126,14 +145,27 @@ public:
      *  @param font    The font to be used for this run.
      *  @param count   Number of glyphs.
      *  @param x,y     Position within the blob.
+     *  @param textByteCount length of the original UTF-8 text that
+     *                 corresponds to this sequence of glyphs.  If 0,
+     *                 text will not be included in the textblob.
+     *  @param lang    Language code, currently unimplemented.
      *  @param bounds  Optional run bounding box. If known in advance (!= NULL), it will
      *                 be used when computing the blob bounds, to avoid re-measuring.
      *
      *  @return        A writable glyph buffer, valid until the next allocRun() or
      *                 build() call. The buffer is guaranteed to hold @count@ glyphs.
      */
+    const RunBuffer& allocRunText(const SkPaint& font,
+                                  int count,
+                                  SkScalar x,
+                                  SkScalar y,
+                                  int textByteCount,
+                                  SkString lang,
+                                  const SkRect* bounds = NULL);
     const RunBuffer& allocRun(const SkPaint& font, int count, SkScalar x, SkScalar y,
-                              const SkRect* bounds = NULL);
+                              const SkRect* bounds = NULL) {
+        return this->allocRunText(font, count, x, y, 0, SkString(), bounds);
+    }
 
     /**
      *  Allocates a new horizontally-positioned run and returns its writable glyph and position
@@ -142,14 +174,23 @@ public:
      *  @param font    The font to be used for this run.
      *  @param count   Number of glyphs.
      *  @param y       Vertical offset within the blob.
+     *  @param textByteCount length of the original UTF-8 text that
+     *                 corresponds to this sequence of glyphs.  If 0,
+     *                 text will not be included in the textblob.
+     *  @param lang    Language code, currently unimplemented.
      *  @param bounds  Optional run bounding box. If known in advance (!= NULL), it will
      *                 be used when computing the blob bounds, to avoid re-measuring.
      *
      *  @return        Writable glyph and position buffers, valid until the next allocRun()
      *                 or build() call. The buffers are guaranteed to hold @count@ elements.
      */
+    const RunBuffer& allocRunTextPosH(const SkPaint& font, int count, SkScalar y,
+                                      int textByteCount, SkString lang,
+                                      const SkRect* bounds = NULL);
     const RunBuffer& allocRunPosH(const SkPaint& font, int count, SkScalar y,
-                                  const SkRect* bounds = NULL);
+                                  const SkRect* bounds = NULL) {
+        return this->allocRunTextPosH(font, count, y, 0, SkString(), bounds);
+    }
 
     /**
      *  Allocates a new fully-positioned run and returns its writable glyph and position
@@ -157,6 +198,10 @@ public:
      *
      *  @param font   The font to be used for this run.
      *  @param count  Number of glyphs.
+     *  @param textByteCount length of the original UTF-8 text that
+     *                 corresponds to this sequence of glyphs.  If 0,
+     *                 text will not be included in the textblob.
+     *  @param lang    Language code, currently unimplemented.
      *  @param bounds Optional run bounding box. If known in advance (!= NULL), it will
      *                be used when computing the blob bounds, to avoid re-measuring.
      *
@@ -164,12 +209,18 @@ public:
      *                or build() call. The glyph buffer and position buffer are
      *                guaranteed to hold @count@ and 2 * @count@ elements, respectively.
      */
-    const RunBuffer& allocRunPos(const SkPaint& font, int count, const SkRect* bounds = NULL);
+    const RunBuffer& allocRunTextPos(const SkPaint& font, int count,
+                                     int textByteCount, SkString lang,
+                                     const SkRect* bounds = NULL);
+    const RunBuffer& allocRunPos(const SkPaint& font, int count,
+                                 const SkRect* bounds = NULL) {
+        return this->allocRunTextPos(font, count, 0, SkString(), bounds);
+    }
 
 private:
     void reserve(size_t size);
     void allocInternal(const SkPaint& font, SkTextBlob::GlyphPositioning positioning,
-                       int count, SkPoint offset, const SkRect* bounds);
+                       int count, int textBytes, SkPoint offset, const SkRect* bounds);
     bool mergeRun(const SkPaint& font, SkTextBlob::GlyphPositioning positioning,
                   int count, SkPoint offset);
     void updateDeferredBounds();
index 3c7345f..35adc24 100644 (file)
@@ -105,17 +105,41 @@ static_assert(sizeof(RunFont) == sizeof(RunFontStorageEquivalent), "runfont_shou
 //
 //  Each run record describes a text blob run, and can be used to determine the (implicit)
 //  location of the following record.
+//
+// Extended Textblob runs have more data after the Pos[] array:
+//
+//    -------------------------------------------------------------------------
+//    ... | RunRecord | Glyphs[] | Pos[] | TextSize | Clusters[] | Text[] | ...
+//    -------------------------------------------------------------------------
+//
+// To determine the length of the extended run data, the TextSize must be read.
+//
+// Extended Textblob runs may be mixed with non-extended runs.
 
 SkDEBUGCODE(static const unsigned kRunRecordMagic = 0xb10bcafe;)
 
+namespace {
+struct RunRecordStorageEquivalent {
+    RunFont  fFont;
+    SkPoint  fOffset;
+    uint32_t fCount;
+    uint32_t fFlags;
+    SkDEBUGCODE(unsigned fMagic;)
+};
+}
+
 class SkTextBlob::RunRecord {
 public:
-    RunRecord(uint32_t count, const SkPoint& offset, const SkPaint& font, GlyphPositioning pos)
+    RunRecord(uint32_t count, uint32_t textSize,  const SkPoint& offset, const SkPaint& font, GlyphPositioning pos)
         : fFont(font)
         , fCount(count)
         , fOffset(offset)
-        , fPositioning(pos) {
+        , fPositioning(pos)
+        , fExtended(textSize > 0) {
         SkDEBUGCODE(fMagic = kRunRecordMagic);
+        if (textSize > 0) {
+            *this->textSizePtr() = textSize;
+        }
     }
 
     uint32_t glyphCount() const {
@@ -135,7 +159,8 @@ public:
     }
 
     uint16_t* glyphBuffer() const {
-        // Glyph are stored immediately following the record.
+        static_assert(SkIsAlignPtr(sizeof(RunRecord)), "");
+        // Glyphs are stored immediately following the record.
         return reinterpret_cast<uint16_t*>(const_cast<RunRecord*>(this) + 1);
     }
 
@@ -145,11 +170,31 @@ public:
                                            SkAlign4(fCount * sizeof(uint16_t)));
     }
 
-    static size_t StorageSize(int glyphCount, SkTextBlob::GlyphPositioning positioning) {
+    uint32_t textSize() const { return fExtended ? *this->textSizePtr() : 0; }
+
+    uint32_t* clusterBuffer() const {
+        // clusters follow the textSize.
+        return fExtended ? 1 + this->textSizePtr() : nullptr;
+    }
+
+    char* textBuffer() const {
+        if (!fExtended) { return nullptr; }
+        return reinterpret_cast<char*>(this->clusterBuffer() + fCount);
+    }
+
+    static size_t StorageSize(int glyphCount, int textSize,
+                              SkTextBlob::GlyphPositioning positioning) {
+        static_assert(SkIsAlign4(sizeof(SkScalar)), "SkScalar size alignment");
         // RunRecord object + (aligned) glyph buffer + position buffer
-        return SkAlignPtr(sizeof(SkTextBlob::RunRecord)
-                        + SkAlign4(glyphCount* sizeof(uint16_t))
-                        + glyphCount * sizeof(SkScalar) * ScalarsPerGlyph(positioning));
+        size_t size = sizeof(SkTextBlob::RunRecord)
+                      + SkAlign4(glyphCount* sizeof(uint16_t))
+                      + PosCount(glyphCount, positioning) * sizeof(SkScalar);
+        if (textSize > 0) {  // Extended run.
+            size += sizeof(uint32_t)
+                + sizeof(uint32_t) * glyphCount
+                + textSize;
+        }
+        return SkAlignPtr(size);
     }
 
     static const RunRecord* First(const SkTextBlob* blob) {
@@ -158,20 +203,41 @@ public:
     }
 
     static const RunRecord* Next(const RunRecord* run) {
-        return reinterpret_cast<const RunRecord*>(reinterpret_cast<const uint8_t*>(run)
-            + StorageSize(run->glyphCount(), run->positioning()));
+        return reinterpret_cast<const RunRecord*>(
+                reinterpret_cast<const uint8_t*>(run)
+                + StorageSize(run->glyphCount(), run->textSize(), run->positioning()));
     }
 
     void validate(const uint8_t* storageTop) const {
         SkASSERT(kRunRecordMagic == fMagic);
         SkASSERT((uint8_t*)Next(this) <= storageTop);
+
         SkASSERT(glyphBuffer() + fCount <= (uint16_t*)posBuffer());
         SkASSERT(posBuffer() + fCount * ScalarsPerGlyph(fPositioning) <= (SkScalar*)Next(this));
+        if (fExtended) {
+            SkASSERT(textSize() > 0);
+            SkASSERT(textSizePtr() < (uint32_t*)Next(this));
+            SkASSERT(clusterBuffer() < (uint32_t*)Next(this));
+            SkASSERT(textBuffer() + textSize() <= (char*)Next(this));
+        }
+        static_assert(sizeof(SkTextBlob::RunRecord) == sizeof(RunRecordStorageEquivalent),
+                      "runrecord_should_stay_packed");
     }
 
 private:
     friend class SkTextBlobBuilder;
 
+    static size_t PosCount(int glyphCount,
+                           SkTextBlob::GlyphPositioning positioning) {
+        return glyphCount * ScalarsPerGlyph(positioning);
+    }
+    
+    uint32_t* textSizePtr() const {
+        // textSize follows the position buffer.
+        SkASSERT(fExtended);
+        return (uint32_t*)(&this->posBuffer()[PosCount(fCount, fPositioning)]);
+    }
+
     void grow(uint32_t count) {
         SkScalar* initialPosBuffer = posBuffer();
         uint32_t initialCount = fCount;
@@ -189,6 +255,7 @@ private:
     uint32_t         fCount;
     SkPoint          fOffset;
     GlyphPositioning fPositioning;
+    bool             fExtended;
 
     SkDEBUGCODE(unsigned fMagic;)
 };
@@ -218,6 +285,17 @@ SkTextBlob::~SkTextBlob() {
     }
 }
 
+namespace {
+union PositioningAndExtended {
+    int32_t intValue;
+    struct {
+        SkTextBlob::GlyphPositioning positioning;
+        bool extended;
+        uint16_t padding;
+    };
+};
+} // namespace
+
 void SkTextBlob::flatten(SkWriteBuffer& buffer) const {
     int runCount = fRunCount;
 
@@ -230,7 +308,17 @@ void SkTextBlob::flatten(SkWriteBuffer& buffer) const {
         SkASSERT(it.glyphCount() > 0);
 
         buffer.write32(it.glyphCount());
-        buffer.write32(it.positioning());
+        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);
@@ -239,6 +327,10 @@ void SkTextBlob::flatten(SkWriteBuffer& buffer) const {
         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();
         SkDEBUGCODE(runCount--);
@@ -258,10 +350,14 @@ sk_sp<SkTextBlob> SkTextBlob::MakeFromBuffer(SkReadBuffer& reader) {
     SkTextBlobBuilder blobBuilder;
     for (int i = 0; i < runCount; ++i) {
         int glyphCount = reader.read32();
-        GlyphPositioning pos = static_cast<GlyphPositioning>(reader.read32());
+
+        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);
@@ -271,13 +367,15 @@ sk_sp<SkTextBlob> SkTextBlob::MakeFromBuffer(SkReadBuffer& reader) {
         const SkTextBlobBuilder::RunBuffer* buf = nullptr;
         switch (pos) {
         case kDefault_Positioning:
-            buf = &blobBuilder.allocRun(font, glyphCount, offset.x(), offset.y(), &bounds);
+            buf = &blobBuilder.allocRunText(font, glyphCount, offset.x(), offset.y(),
+                                            textSize, SkString(), &bounds);
             break;
         case kHorizontal_Positioning:
-            buf = &blobBuilder.allocRunPosH(font, glyphCount, offset.y(), &bounds);
+            buf = &blobBuilder.allocRunTextPosH(font, glyphCount, offset.y(),
+                                                textSize, SkString(), &bounds);
             break;
         case kFull_Positioning:
-            buf = &blobBuilder.allocRunPos(font, glyphCount, &bounds);
+            buf = &blobBuilder.allocRunTextPos(font, glyphCount, textSize, SkString(), &bounds);
             break;
         default:
             return nullptr;
@@ -288,6 +386,13 @@ sk_sp<SkTextBlob> SkTextBlob::MakeFromBuffer(SkReadBuffer& reader) {
                                   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();
@@ -350,6 +455,20 @@ void SkTextBlobRunIterator::applyFontToPaint(SkPaint* paint) const {
     fCurrentRun->font().applyToPaint(paint);
 }
 
+uint32_t* SkTextBlobRunIterator::clusters() const {
+    SkASSERT(!this->done());
+    return fCurrentRun->clusterBuffer();
+}
+uint32_t SkTextBlobRunIterator::textSize() const {
+    SkASSERT(!this->done());
+    return fCurrentRun->textSize();
+}
+char* SkTextBlobRunIterator::text() const {
+    SkASSERT(!this->done());
+    return fCurrentRun->textBuffer();
+}
+
+
 bool SkTextBlobRunIterator::isLCD() const {
     return SkToBool(fCurrentRun->font().flags() & SkPaint::kLCDRenderText_Flag);
 }
@@ -513,6 +632,10 @@ bool SkTextBlobBuilder::mergeRun(const SkPaint &font, SkTextBlob::GlyphPositioni
                                                                           fLastRun);
     SkASSERT(run->glyphCount() > 0);
 
+    if (run->textSize() != 0) {
+        return false;
+    }
+
     if (run->positioning() != positioning
         || run->font() != font
         || (run->glyphCount() + count < run->glyphCount())) {
@@ -529,8 +652,8 @@ bool SkTextBlobBuilder::mergeRun(const SkPaint &font, SkTextBlob::GlyphPositioni
         return false;
     }
 
-    size_t sizeDelta = SkTextBlob::RunRecord::StorageSize(run->glyphCount() + count, positioning) -
-                       SkTextBlob::RunRecord::StorageSize(run->glyphCount(), positioning);
+    size_t sizeDelta = SkTextBlob::RunRecord::StorageSize(run->glyphCount() + count, 0, positioning) -
+                       SkTextBlob::RunRecord::StorageSize(run->glyphCount(), 0, positioning);
     this->reserve(sizeDelta);
 
     // reserve may have realloced
@@ -553,24 +676,25 @@ bool SkTextBlobBuilder::mergeRun(const SkPaint &font, SkTextBlob::GlyphPositioni
 
 void SkTextBlobBuilder::allocInternal(const SkPaint &font,
                                       SkTextBlob::GlyphPositioning positioning,
-                                      int count, SkPoint offset, const SkRect* bounds) {
+                                      int count, int textSize, SkPoint offset, const SkRect* bounds) {
     SkASSERT(count > 0);
+    SkASSERT(textSize >= 0);
     SkASSERT(SkPaint::kGlyphID_TextEncoding == font.getTextEncoding());
-
-    if (!this->mergeRun(font, positioning, count, offset)) {
+    if (textSize != 0 || !this->mergeRun(font, positioning, count, offset)) {
         this->updateDeferredBounds();
 
-        size_t runSize = SkTextBlob::RunRecord::StorageSize(count, positioning);
+        size_t runSize = SkTextBlob::RunRecord::StorageSize(count, textSize, positioning);
         this->reserve(runSize);
 
         SkASSERT(fStorageUsed >= sizeof(SkTextBlob));
         SkASSERT(fStorageUsed + runSize <= fStorageSize);
 
         SkTextBlob::RunRecord* run = new (fStorage.get() + fStorageUsed)
-                                         SkTextBlob::RunRecord(count, offset, font, positioning);
-
+            SkTextBlob::RunRecord(count, textSize, offset, font, positioning);
         fCurrentRunBuffer.glyphs = run->glyphBuffer();
         fCurrentRunBuffer.pos = run->posBuffer();
+        fCurrentRunBuffer.utf8text = run->textBuffer();
+        fCurrentRunBuffer.clusters = run->clusterBuffer();
 
         fLastRun = fStorageUsed;
         fStorageUsed += runSize;
@@ -579,7 +703,8 @@ void SkTextBlobBuilder::allocInternal(const SkPaint &font,
         SkASSERT(fStorageUsed <= fStorageSize);
         run->validate(fStorage.get() + fStorageUsed);
     }
-
+    SkASSERT(textSize > 0 || nullptr == fCurrentRunBuffer.utf8text);
+    SkASSERT(textSize > 0 || nullptr == fCurrentRunBuffer.clusters);
     if (!fDeferredBounds) {
         if (bounds) {
             fBounds.join(*bounds);
@@ -589,26 +714,31 @@ void SkTextBlobBuilder::allocInternal(const SkPaint &font,
     }
 }
 
-const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRun(const SkPaint& font, int count,
-                                                                SkScalar x, SkScalar y,
-                                                                const SkRect* bounds) {
-    this->allocInternal(font, SkTextBlob::kDefault_Positioning, count, SkPoint::Make(x, y), bounds);
-
+const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunText(const SkPaint& font, int count,
+                                                                    SkScalar x, SkScalar y,
+                                                                    int textByteCount,
+                                                                    SkString lang,
+                                                                    const SkRect* bounds) {
+    this->allocInternal(font, SkTextBlob::kDefault_Positioning, count, textByteCount, SkPoint::Make(x, y), bounds);
     return fCurrentRunBuffer;
 }
 
-const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunPosH(const SkPaint& font, int count,
-                                                                    SkScalar y,
-                                                                    const SkRect* bounds) {
-    this->allocInternal(font, SkTextBlob::kHorizontal_Positioning, count, SkPoint::Make(0, y),
+const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunTextPosH(const SkPaint& font, int count,
+                                                                        SkScalar y,
+                                                                        int textByteCount,
+                                                                        SkString lang,
+                                                                        const SkRect* bounds) {
+    this->allocInternal(font, SkTextBlob::kHorizontal_Positioning, count, textByteCount, SkPoint::Make(0, y),
                         bounds);
 
     return fCurrentRunBuffer;
 }
 
-const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunPos(const SkPaint& font, int count,
-                                                                   const SkRect *bounds) {
-    this->allocInternal(font, SkTextBlob::kFull_Positioning, count, SkPoint::Make(0, 0), bounds);
+const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunTextPos(const SkPaint& font, int count,
+                                                                       int textByteCount,
+                                                                       SkString lang,
+                                                                       const SkRect *bounds) {
+   this->allocInternal(font, SkTextBlob::kFull_Positioning, count, textByteCount, SkPoint::Make(0, 0), bounds);
 
     return fCurrentRunBuffer;
 }
@@ -631,7 +761,8 @@ sk_sp<SkTextBlob> SkTextBlobBuilder::make() {
         size_t validateSize = sizeof(SkTextBlob);
         const SkTextBlob::RunRecord* run = SkTextBlob::RunRecord::First(blob);
         for (int i = 0; i < fRunCount; ++i) {
-            validateSize += SkTextBlob::RunRecord::StorageSize(run->fCount, run->fPositioning);
+            validateSize += SkTextBlob::RunRecord::StorageSize(
+                    run->fCount, run->textSize(), run->fPositioning);
             run->validate(reinterpret_cast<const uint8_t*>(blob) + fStorageUsed);
             run = SkTextBlob::RunRecord::Next(run);
         }
index 42bbac0..2f1477b 100644 (file)
@@ -28,6 +28,10 @@ public:
     const SkPoint& offset() const;
     void applyFontToPaint(SkPaint*) const;
     SkTextBlob::GlyphPositioning positioning() const;
+    uint32_t* clusters() const;
+    uint32_t textSize() const;
+    char* text() const;
+
     bool isLCD() const;
 
 private:
index 6107024..82bbb21 100644 (file)
@@ -349,3 +349,40 @@ DEF_TEST(TextBlob_builder, reporter) {
 DEF_TEST(TextBlob_paint, reporter) {
     TextBlobTester::TestPaintProps(reporter);
 }
+
+DEF_TEST(TextBlob_extended, reporter) {
+    SkTextBlobBuilder textBlobBuilder;
+    SkPaint paint;
+    const char text1[] = "Foo";
+    const char text2[] = "Bar";
+
+    int glyphCount = paint.textToGlyphs(text1, strlen(text1), nullptr);
+    SkAutoTMalloc<uint16_t> glyphs(glyphCount);
+    (void)paint.textToGlyphs(text1, strlen(text1), glyphs.get());
+    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+    auto run = textBlobBuilder.allocRunText(
+            paint, glyphCount, 0, 0, SkToInt(strlen(text2)), SkString(), nullptr);
+    memcpy(run.glyphs, glyphs.get(), sizeof(uint16_t) * glyphCount);
+    memcpy(run.utf8text, text2, strlen(text2));
+    for (int i = 0; i < glyphCount; ++i) {
+        run.clusters[i] = SkTMin(SkToU32(i), SkToU32(strlen(text2)));
+    }
+    sk_sp<const SkTextBlob> blob(textBlobBuilder.build());
+    REPORTER_ASSERT(reporter, blob);
+
+    for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
+        REPORTER_ASSERT(reporter, it.glyphCount() == (uint32_t)glyphCount);
+        for (uint32_t i = 0; i < it.glyphCount(); ++i) {
+            REPORTER_ASSERT(reporter, it.glyphs()[i] == glyphs[i]);
+        }
+        REPORTER_ASSERT(reporter, SkTextBlob::kDefault_Positioning == it.positioning());
+        REPORTER_ASSERT(reporter, (SkPoint{0.0f, 0.0f}) == it.offset());
+        REPORTER_ASSERT(reporter, it.textSize() > 0);
+        REPORTER_ASSERT(reporter, it.clusters());
+        for (uint32_t i = 0; i < it.glyphCount(); ++i) {
+            REPORTER_ASSERT(reporter, i == it.clusters()[i]);
+        }
+        REPORTER_ASSERT(reporter, 0 == strncmp(text2, it.text(), it.textSize()));
+    }
+}
index 29dd1b0..2c4d211 100644 (file)
@@ -105,7 +105,9 @@ SkScalar SkShaper::shape(SkTextBlobBuilder* builder,
     hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, NULL);
     hb_glyph_position_t* pos =
             hb_buffer_get_glyph_positions(buffer, NULL);
-    auto runBuffer = builder->allocRunPos(paint, len);
+    auto runBuffer = builder->allocRunTextPos(
+            paint, SkToInt(len), SkToInt(textBytes), SkString());
+    memcpy(runBuffer.utf8text, utf8text, textBytes);
 
     double x = point.x();
     double y = point.y();
@@ -115,12 +117,14 @@ SkScalar SkShaper::shape(SkTextBlobBuilder* builder,
 
     for (unsigned i = 0; i < len; i++) {
         runBuffer.glyphs[i] = info[i].codepoint;
+        runBuffer.clusters[i] = info[i].cluster;
         reinterpret_cast<SkPoint*>(runBuffer.pos)[i] =
                 SkPoint::Make(SkDoubleToScalar(x + pos[i].x_offset * textSizeX),
                               SkDoubleToScalar(y - pos[i].y_offset * textSizeY));
         x += pos[i].x_advance * textSizeX;
         y += pos[i].y_advance * textSizeY;
     }
+
     hb_buffer_clear_contents(buffer);
     return (SkScalar)x;
 }
index f17a26d..d405c71 100644 (file)
@@ -151,7 +151,7 @@ public:
         }
         SkTextBlobBuilder textBlobBuilder;
         shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, SkPoint{0, 0});
-        sk_sp<const SkTextBlob> blob(textBlobBuilder.build());
+        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
         pageCanvas->drawTextBlob(
                 blob.get(), SkDoubleToScalar(current_x),
                 SkDoubleToScalar(current_y), glyph_paint);