174 lines
4.8 KiB
C
174 lines
4.8 KiB
C
|
/*
|
||
|
* Some additional glue functions for using Harfbuzz with
|
||
|
* custom allocators.
|
||
|
*/
|
||
|
|
||
|
#include "mupdf/fitz.h"
|
||
|
#include "fitz-imp.h"
|
||
|
|
||
|
#include "hb.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
|
||
|
/* Harfbuzz has some major design flaws (for our usage
|
||
|
* at least).
|
||
|
*
|
||
|
* By default it uses malloc and free as the underlying
|
||
|
* allocators. Thus in its default form we cannot get
|
||
|
* a record (much less control) over how much allocation
|
||
|
* is done.
|
||
|
*
|
||
|
* Harfbuzz does allow build options to control where
|
||
|
* malloc and free go - in particular we point them at
|
||
|
* fz_hb_malloc and fz_hb_free in our implementation.
|
||
|
* Unfortunately, this has problems too.
|
||
|
*
|
||
|
* Firstly, there is no mechanism for getting a context
|
||
|
* through the call. Most other libraries allow us to
|
||
|
* pass a "void *" value in, and have it passed through
|
||
|
* to arrive unchanged at the allocator functions.
|
||
|
*
|
||
|
* Without this rudimentary functionality, we are forced
|
||
|
* to serialise all access to Harfbuzz.
|
||
|
*
|
||
|
* By taking a mutex around all calls to Harfbuzz, we
|
||
|
* can use a static of our own to get a fz_context safely
|
||
|
* through to the allocators. This obviously costs us
|
||
|
* performance in the multi-threaded case.
|
||
|
*
|
||
|
* This does not protect us against the possibility of
|
||
|
* other people calling harfbuzz; for instance, if we
|
||
|
* link MuPDF into an app that either calls harfbuzz
|
||
|
* itself, or uses another library that calls harfbuzz,
|
||
|
* there is no guarantee that that library will take
|
||
|
* the same lock while calling harfbuzz. This leaves
|
||
|
* us open to the possibility of crashes. The only
|
||
|
* way around this would be to use completely separate
|
||
|
* harfbuzz instances.
|
||
|
*
|
||
|
* In order to ensure that allocations throughout mupdf
|
||
|
* are done consistently, we get harfbuzz to call our
|
||
|
* own fz_hb_malloc/realloc/calloc/free functions that
|
||
|
* call down to fz_malloc/realloc/calloc/free. These
|
||
|
* require context variables, so we get our fz_hb_lock
|
||
|
* and unlock to set these. Any attempt to call through
|
||
|
* without setting these will be detected.
|
||
|
*
|
||
|
* It is therefore vital that any fz_lock/fz_unlock
|
||
|
* handlers are shared between all the fz_contexts in
|
||
|
* use at a time.
|
||
|
*
|
||
|
* Secondly, Harfbuzz allocates some 'internal' memory
|
||
|
* on the first call, and leaves this linked from static
|
||
|
* variables. By default, this data is never freed back.
|
||
|
* This means it is impossible to clear the library back
|
||
|
* to a default state. Memory debugging will always show
|
||
|
* Harfbuzz as having leaked a set amount of memory.
|
||
|
*
|
||
|
* There is a mechanism in Harfbuzz for freeing these
|
||
|
* blocks - that of building with HAVE_ATEXIT. This
|
||
|
* causes the blocks to be freed back on exit, but a)
|
||
|
* this doesn't reset the fz_context value, so we can't
|
||
|
* free them correctly, and b) any fz_context value it
|
||
|
* did keep would already have been closed down due to
|
||
|
* the program exit.
|
||
|
*
|
||
|
* In addition, because of these everlasting blocks, we
|
||
|
* cannot safely call Harfbuzz after we close down any
|
||
|
* allocator that Harfbuzz has been using (because
|
||
|
* Harfbuzz may still be holding pointers to data within
|
||
|
* that allocators managed space).
|
||
|
*
|
||
|
* There is nothing we can do about the leaking blocks
|
||
|
* except to add some hacks to our memory debugging
|
||
|
* library to allow it to suppress the blocks that
|
||
|
* harfbuzz leaks.
|
||
|
*
|
||
|
* Consequently, we leave them to leak, and warn Memento
|
||
|
* about this.
|
||
|
*/
|
||
|
|
||
|
/* Potentially we can write different versions
|
||
|
* of get_context and set_context for different
|
||
|
* threading systems.
|
||
|
*
|
||
|
* This simple version relies on harfbuzz never
|
||
|
* trying to make 2 allocations at once on
|
||
|
* different threads. The only way that can happen
|
||
|
* is when one of those other threads is someone
|
||
|
* outside MuPDF calling harfbuzz while MuPDF
|
||
|
* is running. This will cause us such huge
|
||
|
* problems that for now, we'll just forbid it.
|
||
|
*/
|
||
|
|
||
|
static fz_context *fz_hb_secret = NULL;
|
||
|
|
||
|
static void set_hb_context(fz_context *ctx)
|
||
|
{
|
||
|
fz_hb_secret = ctx;
|
||
|
}
|
||
|
|
||
|
static fz_context *get_hb_context(void)
|
||
|
{
|
||
|
return fz_hb_secret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Lock against Harfbuzz being called
|
||
|
simultaneously in several threads. This reuses
|
||
|
FZ_LOCK_FREETYPE.
|
||
|
*/
|
||
|
void fz_hb_lock(fz_context *ctx)
|
||
|
{
|
||
|
fz_lock(ctx, FZ_LOCK_FREETYPE);
|
||
|
|
||
|
set_hb_context(ctx);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Unlock after a Harfbuzz call. This reuses
|
||
|
FZ_LOCK_FREETYPE.
|
||
|
*/
|
||
|
void fz_hb_unlock(fz_context *ctx)
|
||
|
{
|
||
|
set_hb_context(NULL);
|
||
|
|
||
|
fz_unlock(ctx, FZ_LOCK_FREETYPE);
|
||
|
}
|
||
|
|
||
|
void *fz_hb_malloc(size_t size)
|
||
|
{
|
||
|
fz_context *ctx = get_hb_context();
|
||
|
|
||
|
assert(ctx != NULL);
|
||
|
|
||
|
return Memento_label(fz_malloc_no_throw(ctx, size), "hb");
|
||
|
}
|
||
|
|
||
|
void *fz_hb_calloc(size_t n, size_t size)
|
||
|
{
|
||
|
fz_context *ctx = get_hb_context();
|
||
|
|
||
|
assert(ctx != NULL);
|
||
|
|
||
|
return Memento_label(fz_calloc_no_throw(ctx, n, size), "hb");
|
||
|
}
|
||
|
|
||
|
void *fz_hb_realloc(void *ptr, size_t size)
|
||
|
{
|
||
|
fz_context *ctx = get_hb_context();
|
||
|
|
||
|
assert(ctx != NULL);
|
||
|
|
||
|
return Memento_label(fz_realloc_no_throw(ctx, ptr, size), "hb");
|
||
|
}
|
||
|
|
||
|
void fz_hb_free(void *ptr)
|
||
|
{
|
||
|
fz_context *ctx = get_hb_context();
|
||
|
|
||
|
assert(ctx != NULL);
|
||
|
|
||
|
fz_free(ctx, ptr);
|
||
|
}
|