blob: cd5254dcaa9b6e785b7da049a4a1221b9c13711a [file] [edit]
/*
* Copyright © 2009 Red Hat, Inc.
* Copyright © 2018 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Red Hat Author(s): Behdad Esfahbod
* Google Author(s): Behdad Esfahbod
*/
#ifndef HB_BLOB_HH
#define HB_BLOB_HH
#include "hb.hh"
/*
* hb_blob_t
*/
struct hb_blob_t
{
~hb_blob_t () { destroy_user_data (); }
void destroy_user_data ()
{
if (destroy)
{
destroy (user_data);
user_data = nullptr;
destroy = nullptr;
}
}
void replace_buffer (const char *new_data,
unsigned new_length,
hb_memory_mode_t new_mode,
void *new_user_data,
hb_destroy_func_t new_destroy)
{
if (new_data != data)
destroy_user_data ();
data = new_data;
length = new_length;
mode = new_mode;
user_data = new_user_data;
destroy = new_destroy;
}
HB_INTERNAL bool try_make_writable ();
HB_INTERNAL bool try_make_writable_inplace ();
HB_INTERNAL bool try_make_writable_inplace_unix ();
hb_bytes_t as_bytes () const { return hb_bytes_t (data, length); }
template <typename Type>
const Type* as () const { return as_bytes ().as<Type> (); }
public:
hb_object_header_t header;
const char *data = nullptr;
unsigned int length = 0;
hb_memory_mode_t mode = (hb_memory_mode_t) 0;
void *user_data = nullptr;
hb_destroy_func_t destroy = nullptr;
/*
* Blob-recycling helpers.
*
* Encoders that produce a sized byte buffer and wrap it as an
* hb_blob_t can amortize malloc/blob-allocation across repeated
* renders by handing the output blob back via a recycle slot. On
* the next encode these helpers reuse (or realloc) the buffer and
* the same hb_blob_t handle is returned, skipping malloc/free
* and blob-handle churn across glyph-by-glyph encoding loops.
*
* Blobs managed by this machinery are identified by the address
* of recycle_data_destroy.
*/
struct recycle_data_t
{
char *buf;
unsigned capacity;
};
static inline void recycle_data_destroy (void *user_data)
{
auto *bd = (recycle_data_t *) user_data;
hb_free (bd->buf);
hb_free (bd);
}
/* Acquire a buffer of at least @needed bytes. If @recycled is
* one of our blobs, reuse its buffer (or realloc it).
* *@out_capacity receives the actual capacity (>= @needed).
* *@out_replaced_buf is set to the recycled buf when realloc
* fails and a fresh buffer was allocated instead -- the caller
* must hb_free() that buf after recycle_finalize() runs. Returns
* nullptr on allocation failure. */
static inline char *
recycle_acquire (hb_blob_t *recycled,
unsigned needed,
unsigned *out_capacity,
char **out_replaced_buf)
{
*out_replaced_buf = nullptr;
if (recycled && recycled->destroy == recycle_data_destroy)
{
auto *bd = (recycle_data_t *) recycled->user_data;
if (bd->capacity >= needed)
{
*out_capacity = bd->capacity;
return bd->buf;
}
/* Grow with a 1.5x ramp to amortize repeated growth. */
unsigned alloc_bytes = needed;
if (unlikely (hb_unsigned_add_overflows (needed, needed / 2,
&alloc_bytes)))
alloc_bytes = needed;
char *new_buf = (char *) hb_realloc (bd->buf, alloc_bytes);
if (new_buf)
{
bd->buf = new_buf;
bd->capacity = alloc_bytes;
*out_capacity = alloc_bytes;
return new_buf;
}
/* Realloc failed. Fall through to a fresh hb_malloc and stash
* the old buf for the caller to free after recycle_finalize. */
*out_replaced_buf = bd->buf;
}
char *buf = (char *) hb_malloc (needed);
if (unlikely (!buf))
return nullptr;
*out_capacity = needed;
return buf;
}
/* Wrap @buf (of @capacity, with @length used) into an hb_blob_t.
* If @recycled is one of our blobs, update and return it (cheap);
* otherwise create a new blob. Pass @replaced_recycled_buf from
* recycle_acquire(). */
static inline hb_blob_t *
recycle_finalize (char *buf,
unsigned capacity,
unsigned length,
hb_blob_t *recycled,
char *replaced_recycled_buf)
{
if (recycled && recycled->destroy == recycle_data_destroy)
{
auto *bd = (recycle_data_t *) recycled->user_data;
if (replaced_recycled_buf && replaced_recycled_buf != buf)
hb_free (replaced_recycled_buf);
bd->buf = buf;
bd->capacity = capacity;
recycled->data = (const char *) buf;
recycled->length = length;
return recycled;
}
/* No recycled blob to update -- create a fresh one with our
* destroy closure so the next recycle round can reuse it. */
recycle_data_t *bd = (recycle_data_t *) hb_malloc (sizeof (*bd));
if (unlikely (!bd))
{
hb_free (buf);
return nullptr;
}
bd->buf = buf;
bd->capacity = capacity;
return hb_blob_create ((const char *) buf, length,
HB_MEMORY_MODE_WRITABLE,
bd, recycle_data_destroy);
}
/* Discard @buf returned by recycle_acquire without committing to
* a blob. Frees @buf if it was a fresh allocation; leaves any
* recycled buffer untouched. */
static inline void
recycle_abort (char *buf, hb_blob_t *recycled)
{
if (!buf) return;
if (recycled && recycled->destroy == recycle_data_destroy)
{
auto *bd = (recycle_data_t *) recycled->user_data;
if (buf == bd->buf) return; /* owned by the recycled blob */
}
hb_free (buf);
}
/* Stash @blob in @slot as the recycled output for the next
* encode. Destroys any previously stashed blob. Safe to call
* with @blob = nullptr or the empty-singleton blob (treated as
* "drop"). */
static inline void
recycle_stash (hb_blob_t **slot, hb_blob_t *blob)
{
hb_blob_destroy (*slot);
*slot = nullptr;
if (!blob || blob == hb_blob_get_empty ())
return;
*slot = blob;
}
};
/*
* hb_blob_ptr_t
*/
template <typename P>
struct hb_blob_ptr_t
{
typedef hb_remove_pointer<P> T;
hb_blob_ptr_t (hb_blob_t *b_ = nullptr) : b (b_) {}
hb_blob_t * operator = (hb_blob_t *b_) { return b = b_; }
const T * operator -> () const { return get (); }
const T & operator * () const { return *get (); }
template <typename C> operator const C * () const { return get (); }
operator const char * () const { return (const char *) get (); }
const T * get () const { return b->as<T> (); }
hb_blob_t * get_blob () const { return b.get_raw (); }
unsigned int get_length () const { return b.get ()->length; }
void destroy () { hb_blob_destroy (b.get_raw ()); b = nullptr; }
private:
hb_nonnull_ptr_t<hb_blob_t> b;
};
#endif /* HB_BLOB_HH */