/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkTextBlob_DEFINED
#define SkTextBlob_DEFINED

#include "../private/SkTemplates.h"
#include "../private/SkAtomics.h"
#include "SkPaint.h"
#include "SkString.h"
#include "SkRefCnt.h"

struct SkSerialProcs;
struct SkDeserialProcs;

/** \class SkTextBlob

    SkTextBlob combines multiple text runs into an immutable, ref-counted structure.
*/
class SK_API SkTextBlob final : public SkNVRefCnt<SkTextBlob> {
public:
    /**
     *  Returns a conservative blob bounding box.
     */
    const SkRect& bounds() const { return fBounds; }

    /**
     *  Return a non-zero, unique value representing the text blob.
     */
    uint32_t uniqueID() const { return fUniqueID; }

    static sk_sp<SkTextBlob> MakeFromText(
            const void* text, size_t byteLength, const SkPaint& paint);

    /**
     *  Similar to serialize above, but writes directly into |memory|. Returns bytes written or 0u
     *  if serialization failed due to insufficient size.
     */
    size_t serialize(const SkSerialProcs& procs, void* memory, size_t memory_size) const;

    sk_sp<SkData> serialize(const SkSerialProcs& procs) const;

    static sk_sp<SkTextBlob> Deserialize(const void* data, size_t size,
                                         const SkDeserialProcs& procs);

private:
    friend class SkNVRefCnt<SkTextBlob>;
    class RunRecord;

    enum GlyphPositioning : uint8_t;

    explicit SkTextBlob(const SkRect& bounds);

    ~SkTextBlob();

    // Memory for objects of this class is created with sk_malloc rather than operator new and must
    // be freed with sk_free.
    void operator delete(void* p);
    void* operator new(size_t);
    void* operator new(size_t, void* p);

    static unsigned ScalarsPerGlyph(GlyphPositioning pos);

    // Call when this blob is part of the key to a cache entry. This allows the cache
    // to know automatically those entries can be purged when this SkTextBlob is deleted.
    void notifyAddedToCache(uint32_t cacheID) const {
        fCacheID.store(cacheID);
    }

    friend class SkGlyphRunList;
    friend class GrTextBlobCache;
    friend class SkTextBlobBuilder;
    friend class SkTextBlobPriv;
    friend class SkTextBlobRunIterator;

    const SkRect               fBounds;
    const uint32_t             fUniqueID;
    mutable SkAtomic<uint32_t> fCacheID;

    SkDEBUGCODE(size_t fStorageSize;)

    // The actual payload resides in externally-managed storage, following the object.
    // (see the .cpp for more details)

    typedef SkRefCnt INHERITED;
};

/** \class SkTextBlobBuilder

    Helper class for constructing SkTextBlobs.
 */
class SK_API SkTextBlobBuilder {
public:
    SkTextBlobBuilder();

    ~SkTextBlobBuilder();

    /**
     *  Returns an immutable SkTextBlob for the current runs/glyphs,
     *  or nullptr if no runs were allocated.
     *
     *  The builder is reset and can be reused.
     */
    sk_sp<SkTextBlob> make();

    /**
     *  Glyph and position buffers associated with a run.
     *
     *  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;
    };

    /**
     *  Allocates a new default-positioned run and returns its writable glyph buffer
     *  for direct manipulation.
     *
     *  @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 = nullptr);
    const RunBuffer& allocRun(const SkPaint& font, int count, SkScalar x, SkScalar y,
                              const SkRect* bounds = nullptr) {
        return this->allocRunText(font, count, x, y, 0, SkString(), bounds);
    }

    /**
     *  Allocates a new horizontally-positioned run and returns its writable glyph and position
     *  buffers for direct manipulation.
     *
     *  @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 = nullptr);
    const RunBuffer& allocRunPosH(const SkPaint& font, int count, SkScalar y,
                                  const SkRect* bounds = nullptr) {
        return this->allocRunTextPosH(font, count, y, 0, SkString(), bounds);
    }

    /**
     *  Allocates a new fully-positioned run and returns its writable glyph and position
     *  buffers for direct manipulation.
     *
     *  @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.
     *
     *  @return       Writable glyph and position buffers, valid until the next allocRun()
     *                or build() call. The glyph buffer and position buffer are
     *                guaranteed to hold @count@ and 2 * @count@ elements, respectively.
     */
    const RunBuffer& allocRunTextPos(const SkPaint& font, int count,
                                     int textByteCount, SkString lang,
                                     const SkRect* bounds = nullptr);
    const RunBuffer& allocRunPos(const SkPaint& font, int count,
                                 const SkRect* bounds = nullptr) {
        return this->allocRunTextPos(font, count, 0, SkString(), bounds);
    }

private:
    void reserve(size_t size);
    void allocInternal(const SkPaint& font, SkTextBlob::GlyphPositioning positioning,
                       int count, int textBytes, SkPoint offset, const SkRect* bounds);
    bool mergeRun(const SkPaint& font, SkTextBlob::GlyphPositioning positioning,
                  uint32_t count, SkPoint offset);
    void updateDeferredBounds();

    static SkRect ConservativeRunBounds(const SkTextBlob::RunRecord&);
    static SkRect TightRunBounds(const SkTextBlob::RunRecord&);

    SkAutoTMalloc<uint8_t> fStorage;
    size_t                 fStorageSize;
    size_t                 fStorageUsed;

    SkRect                 fBounds;
    int                    fRunCount;
    bool                   fDeferredBounds;
    size_t                 fLastRun; // index into fStorage

    RunBuffer              fCurrentRunBuffer;
};

#endif // SkTextBlob_DEFINED
