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 | |