| 1 | /* |
| 2 | * Some additional glue functions for using Harfbuzz with |
| 3 | * custom allocators. |
| 4 | */ |
| 5 | |
| 6 | #include "mupdf/fitz.h" |
| 7 | #include "fitz-imp.h" |
| 8 | |
| 9 | #include "hb.h" |
| 10 | |
| 11 | #include <assert.h> |
| 12 | |
| 13 | /* Harfbuzz has some major design flaws (for our usage |
| 14 | * at least). |
| 15 | * |
| 16 | * By default it uses malloc and free as the underlying |
| 17 | * allocators. Thus in its default form we cannot get |
| 18 | * a record (much less control) over how much allocation |
| 19 | * is done. |
| 20 | * |
| 21 | * Harfbuzz does allow build options to control where |
| 22 | * malloc and free go - in particular we point them at |
| 23 | * fz_hb_malloc and fz_hb_free in our implementation. |
| 24 | * Unfortunately, this has problems too. |
| 25 | * |
| 26 | * Firstly, there is no mechanism for getting a context |
| 27 | * through the call. Most other libraries allow us to |
| 28 | * pass a "void *" value in, and have it passed through |
| 29 | * to arrive unchanged at the allocator functions. |
| 30 | * |
| 31 | * Without this rudimentary functionality, we are forced |
| 32 | * to serialise all access to Harfbuzz. |
| 33 | * |
| 34 | * By taking a mutex around all calls to Harfbuzz, we |
| 35 | * can use a static of our own to get a fz_context safely |
| 36 | * through to the allocators. This obviously costs us |
| 37 | * performance in the multi-threaded case. |
| 38 | * |
| 39 | * This does not protect us against the possibility of |
| 40 | * other people calling harfbuzz; for instance, if we |
| 41 | * link MuPDF into an app that either calls harfbuzz |
| 42 | * itself, or uses another library that calls harfbuzz, |
| 43 | * there is no guarantee that that library will take |
| 44 | * the same lock while calling harfbuzz. This leaves |
| 45 | * us open to the possibility of crashes. The only |
| 46 | * way around this would be to use completely separate |
| 47 | * harfbuzz instances. |
| 48 | * |
| 49 | * In order to ensure that allocations throughout mupdf |
| 50 | * are done consistently, we get harfbuzz to call our |
| 51 | * own fz_hb_malloc/realloc/calloc/free functions that |
| 52 | * call down to fz_malloc/realloc/calloc/free. These |
| 53 | * require context variables, so we get our fz_hb_lock |
| 54 | * and unlock to set these. Any attempt to call through |
| 55 | * without setting these will be detected. |
| 56 | * |
| 57 | * It is therefore vital that any fz_lock/fz_unlock |
| 58 | * handlers are shared between all the fz_contexts in |
| 59 | * use at a time. |
| 60 | * |
| 61 | * Secondly, Harfbuzz allocates some 'internal' memory |
| 62 | * on the first call, and leaves this linked from static |
| 63 | * variables. By default, this data is never freed back. |
| 64 | * This means it is impossible to clear the library back |
| 65 | * to a default state. Memory debugging will always show |
| 66 | * Harfbuzz as having leaked a set amount of memory. |
| 67 | * |
| 68 | * There is a mechanism in Harfbuzz for freeing these |
| 69 | * blocks - that of building with HAVE_ATEXIT. This |
| 70 | * causes the blocks to be freed back on exit, but a) |
| 71 | * this doesn't reset the fz_context value, so we can't |
| 72 | * free them correctly, and b) any fz_context value it |
| 73 | * did keep would already have been closed down due to |
| 74 | * the program exit. |
| 75 | * |
| 76 | * In addition, because of these everlasting blocks, we |
| 77 | * cannot safely call Harfbuzz after we close down any |
| 78 | * allocator that Harfbuzz has been using (because |
| 79 | * Harfbuzz may still be holding pointers to data within |
| 80 | * that allocators managed space). |
| 81 | * |
| 82 | * There is nothing we can do about the leaking blocks |
| 83 | * except to add some hacks to our memory debugging |
| 84 | * library to allow it to suppress the blocks that |
| 85 | * harfbuzz leaks. |
| 86 | * |
| 87 | * Consequently, we leave them to leak, and warn Memento |
| 88 | * about this. |
| 89 | */ |
| 90 | |
| 91 | /* Potentially we can write different versions |
| 92 | * of get_context and set_context for different |
| 93 | * threading systems. |
| 94 | * |
| 95 | * This simple version relies on harfbuzz never |
| 96 | * trying to make 2 allocations at once on |
| 97 | * different threads. The only way that can happen |
| 98 | * is when one of those other threads is someone |
| 99 | * outside MuPDF calling harfbuzz while MuPDF |
| 100 | * is running. This will cause us such huge |
| 101 | * problems that for now, we'll just forbid it. |
| 102 | */ |
| 103 | |
| 104 | static fz_context *fz_hb_secret = NULL; |
| 105 | |
| 106 | static void set_hb_context(fz_context *ctx) |
| 107 | { |
| 108 | fz_hb_secret = ctx; |
| 109 | } |
| 110 | |
| 111 | static fz_context *get_hb_context(void) |
| 112 | { |
| 113 | return fz_hb_secret; |
| 114 | } |
| 115 | |
| 116 | /* |
| 117 | Lock against Harfbuzz being called |
| 118 | simultaneously in several threads. This reuses |
| 119 | FZ_LOCK_FREETYPE. |
| 120 | */ |
| 121 | void fz_hb_lock(fz_context *ctx) |
| 122 | { |
| 123 | fz_lock(ctx, FZ_LOCK_FREETYPE); |
| 124 | |
| 125 | set_hb_context(ctx); |
| 126 | } |
| 127 | |
| 128 | /* |
| 129 | Unlock after a Harfbuzz call. This reuses |
| 130 | FZ_LOCK_FREETYPE. |
| 131 | */ |
| 132 | void fz_hb_unlock(fz_context *ctx) |
| 133 | { |
| 134 | set_hb_context(NULL); |
| 135 | |
| 136 | fz_unlock(ctx, FZ_LOCK_FREETYPE); |
| 137 | } |
| 138 | |
| 139 | void *fz_hb_malloc(size_t size) |
| 140 | { |
| 141 | fz_context *ctx = get_hb_context(); |
| 142 | |
| 143 | assert(ctx != NULL); |
| 144 | |
| 145 | return fz_malloc_no_throw(ctx, size); |
| 146 | } |
| 147 | |
| 148 | void *fz_hb_calloc(size_t n, size_t size) |
| 149 | { |
| 150 | fz_context *ctx = get_hb_context(); |
| 151 | |
| 152 | assert(ctx != NULL); |
| 153 | |
| 154 | return fz_calloc_no_throw(ctx, n, size); |
| 155 | } |
| 156 | |
| 157 | void *fz_hb_realloc(void *ptr, size_t size) |
| 158 | { |
| 159 | fz_context *ctx = get_hb_context(); |
| 160 | |
| 161 | assert(ctx != NULL); |
| 162 | |
| 163 | return fz_realloc_no_throw(ctx, ptr, size); |
| 164 | } |
| 165 | |
| 166 | void fz_hb_free(void *ptr) |
| 167 | { |
| 168 | fz_context *ctx = get_hb_context(); |
| 169 | |
| 170 | assert(ctx != NULL); |
| 171 | |
| 172 | fz_free(ctx, ptr); |
| 173 | } |
| 174 | |