/src/mupdf/source/fitz/outline.c
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (C) 2004-2025 Artifex Software, Inc. |
2 | | // |
3 | | // This file is part of MuPDF. |
4 | | // |
5 | | // MuPDF is free software: you can redistribute it and/or modify it under the |
6 | | // terms of the GNU Affero General Public License as published by the Free |
7 | | // Software Foundation, either version 3 of the License, or (at your option) |
8 | | // any later version. |
9 | | // |
10 | | // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY |
11 | | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
12 | | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more |
13 | | // details. |
14 | | // |
15 | | // You should have received a copy of the GNU Affero General Public License |
16 | | // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> |
17 | | // |
18 | | // Alternative licensing terms are available from the licensor. |
19 | | // For commercial licensing, see <https://www.artifex.com/> or contact |
20 | | // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
21 | | // CA 94129, USA, for further information. |
22 | | |
23 | | #include "mupdf/fitz.h" |
24 | | |
25 | | fz_outline_item *fz_outline_iterator_item(fz_context *ctx, fz_outline_iterator *iter) |
26 | 0 | { |
27 | 0 | if (iter->item == NULL) |
28 | 0 | return NULL; |
29 | 0 | return iter->item(ctx, iter); |
30 | 0 | } |
31 | | |
32 | | int fz_outline_iterator_next(fz_context *ctx, fz_outline_iterator *iter) |
33 | 0 | { |
34 | 0 | if (iter->next == NULL) |
35 | 0 | return -1; |
36 | 0 | return iter->next(ctx, iter); |
37 | 0 | } |
38 | | |
39 | | int fz_outline_iterator_prev(fz_context *ctx, fz_outline_iterator *iter) |
40 | 0 | { |
41 | 0 | if (iter->prev == NULL) |
42 | 0 | return -1; |
43 | 0 | return iter->prev(ctx, iter); |
44 | 0 | } |
45 | | |
46 | | int fz_outline_iterator_up(fz_context *ctx, fz_outline_iterator *iter) |
47 | 0 | { |
48 | 0 | if (iter->up == NULL) |
49 | 0 | return -1; |
50 | 0 | return iter->up(ctx, iter); |
51 | 0 | } |
52 | | |
53 | | int fz_outline_iterator_down(fz_context *ctx, fz_outline_iterator *iter) |
54 | 0 | { |
55 | 0 | if (iter->down == NULL) |
56 | 0 | return -1; |
57 | 0 | return iter->down(ctx, iter); |
58 | 0 | } |
59 | | |
60 | | int fz_outline_iterator_insert(fz_context *ctx, fz_outline_iterator *iter, fz_outline_item *item) |
61 | 0 | { |
62 | 0 | if (iter->insert == NULL) |
63 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "Document type does not support Outline editing"); |
64 | 0 | return iter->insert(ctx, iter, item); |
65 | 0 | } |
66 | | |
67 | | int fz_outline_iterator_delete(fz_context *ctx, fz_outline_iterator *iter) |
68 | 0 | { |
69 | 0 | if (iter->del == NULL) |
70 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "Document type does not support Outline editing"); |
71 | 0 | return iter->del(ctx, iter); |
72 | 0 | } |
73 | | |
74 | | void fz_outline_iterator_update(fz_context *ctx, fz_outline_iterator *iter, fz_outline_item *item) |
75 | 0 | { |
76 | 0 | if (iter->update == NULL) |
77 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "Document type does not support Outline editing"); |
78 | 0 | iter->update(ctx, iter, item); |
79 | 0 | } |
80 | | |
81 | | void fz_drop_outline_iterator(fz_context *ctx, fz_outline_iterator *iter) |
82 | 0 | { |
83 | 0 | if (iter == NULL) |
84 | 0 | return; |
85 | 0 | iter->drop(ctx, iter); |
86 | 0 | fz_drop_document(ctx, iter->doc); |
87 | 0 | fz_free(ctx, iter); |
88 | 0 | } |
89 | | |
90 | | static void |
91 | | load_outline_sub(fz_context *ctx, fz_outline_iterator *iter, fz_outline **tail, char **t, char **u) |
92 | 0 | { |
93 | 0 | fz_outline_item *item; |
94 | 0 | fz_outline *node, *onode; |
95 | 0 | int res; |
96 | |
|
97 | 0 | do { |
98 | 0 | item = fz_outline_iterator_item(ctx, iter); |
99 | 0 | if (item == NULL) |
100 | 0 | return; |
101 | | /* Duplicate title and uri first so we can recurse with limited try/catch. */ |
102 | 0 | *t = item->title == NULL ? NULL : fz_strdup(ctx, item->title); |
103 | 0 | *u = item->uri == NULL ? NULL : fz_strdup(ctx, item->uri); |
104 | 0 | node = fz_malloc_struct(ctx, fz_outline); |
105 | 0 | node->is_open = item->is_open; |
106 | 0 | node->refs = 1; |
107 | 0 | node->title = *t; |
108 | 0 | node->uri = *u; |
109 | 0 | node->page.chapter = -1; |
110 | 0 | node->page.page = -1; |
111 | 0 | *t = NULL; |
112 | 0 | *u = NULL; |
113 | 0 | *tail = node; |
114 | 0 | tail = &node->next; |
115 | 0 | onode = node; |
116 | 0 | node = NULL; |
117 | |
|
118 | 0 | onode->page = fz_resolve_link(ctx, iter->doc, onode->uri, &onode->x, &onode->y); |
119 | |
|
120 | 0 | res = fz_outline_iterator_down(ctx, iter); |
121 | 0 | if (res == 0) |
122 | 0 | load_outline_sub(ctx, iter, &onode->down, t, u); |
123 | 0 | if (res >= 0) |
124 | 0 | fz_outline_iterator_up(ctx, iter); |
125 | 0 | } |
126 | 0 | while (fz_outline_iterator_next(ctx, iter) == 0); |
127 | 0 | } |
128 | | |
129 | | fz_outline * |
130 | | fz_load_outline_from_iterator(fz_context *ctx, fz_outline_iterator *iter) |
131 | 0 | { |
132 | 0 | fz_outline *head = NULL; |
133 | 0 | fz_outline **tail = &head; |
134 | 0 | char *title = NULL; |
135 | 0 | char *uri = NULL; |
136 | |
|
137 | 0 | if (iter == NULL) |
138 | 0 | return NULL; |
139 | | |
140 | 0 | fz_try(ctx) |
141 | 0 | load_outline_sub(ctx, iter, tail, &title, &uri); |
142 | 0 | fz_always(ctx) |
143 | 0 | fz_drop_outline_iterator(ctx, iter); |
144 | 0 | fz_catch(ctx) |
145 | 0 | { |
146 | 0 | fz_drop_outline(ctx, head); |
147 | 0 | fz_free(ctx, title); |
148 | 0 | fz_free(ctx, uri); |
149 | 0 | fz_rethrow(ctx); |
150 | 0 | } |
151 | | |
152 | 0 | return head; |
153 | 0 | } |
154 | | |
155 | | fz_outline_iterator *fz_new_outline_iterator_of_size(fz_context *ctx, size_t size, fz_document *doc) |
156 | 0 | { |
157 | 0 | fz_outline_iterator *iter = fz_calloc(ctx, size, 1); |
158 | |
|
159 | 0 | iter->doc = fz_keep_document(ctx, doc); |
160 | |
|
161 | 0 | return iter; |
162 | 0 | } |
163 | | |
164 | | fz_outline * |
165 | | fz_new_outline(fz_context *ctx) |
166 | 0 | { |
167 | 0 | fz_outline *outline = fz_malloc_struct(ctx, fz_outline); |
168 | 0 | outline->refs = 1; |
169 | 0 | return outline; |
170 | 0 | } |
171 | | |
172 | | fz_outline * |
173 | | fz_keep_outline(fz_context *ctx, fz_outline *outline) |
174 | 0 | { |
175 | 0 | return fz_keep_imp(ctx, outline, &outline->refs); |
176 | 0 | } |
177 | | |
178 | | void |
179 | | fz_drop_outline(fz_context *ctx, fz_outline *outline) |
180 | 0 | { |
181 | 0 | while (fz_drop_imp(ctx, outline, &outline->refs)) |
182 | 0 | { |
183 | 0 | fz_outline *next = outline->next; |
184 | 0 | fz_drop_outline(ctx, outline->down); |
185 | 0 | fz_free(ctx, outline->title); |
186 | 0 | fz_free(ctx, outline->uri); |
187 | 0 | fz_free(ctx, outline); |
188 | 0 | outline = next; |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | | typedef struct { |
193 | | fz_outline_iterator super; |
194 | | fz_outline *outline; |
195 | | fz_outline *current; |
196 | | fz_outline_item item; |
197 | | int down_max; |
198 | | int down_len; |
199 | | fz_outline **down_array; |
200 | | } fz_outline_iter_std; |
201 | | |
202 | | static int |
203 | | iter_std_down(fz_context *ctx, fz_outline_iterator *iter_) |
204 | 0 | { |
205 | 0 | fz_outline_iter_std *iter = (fz_outline_iter_std *)iter_; |
206 | |
|
207 | 0 | if (iter->current == NULL) |
208 | 0 | return -1; |
209 | 0 | if (iter->current->down == NULL) |
210 | 0 | return -1; |
211 | | |
212 | 0 | if (iter->down_max == iter->down_len) |
213 | 0 | { |
214 | 0 | int new_max = iter->down_max ? iter->down_max * 2 : 32; |
215 | 0 | iter->down_array = fz_realloc_array(ctx, iter->down_array, new_max, fz_outline *); |
216 | 0 | iter->down_max = new_max; |
217 | 0 | } |
218 | 0 | iter->down_array[iter->down_len++] = iter->current; |
219 | |
|
220 | 0 | iter->current = iter->current->down; |
221 | 0 | return 0; |
222 | 0 | } |
223 | | |
224 | | static int |
225 | | iter_std_up(fz_context *ctx, fz_outline_iterator *iter_) |
226 | 0 | { |
227 | 0 | fz_outline_iter_std *iter = (fz_outline_iter_std *)iter_; |
228 | |
|
229 | 0 | if (iter->current == NULL) |
230 | 0 | return -1; |
231 | 0 | if (iter->down_len == 0) |
232 | 0 | return -1; |
233 | | |
234 | 0 | iter->current = iter->down_array[--iter->down_len]; |
235 | |
|
236 | 0 | return 0; |
237 | 0 | } |
238 | | |
239 | | static int |
240 | | iter_std_next(fz_context *ctx, fz_outline_iterator *iter_) |
241 | 0 | { |
242 | 0 | fz_outline_iter_std *iter = (fz_outline_iter_std *)iter_; |
243 | |
|
244 | 0 | if (iter->current == NULL) |
245 | 0 | return -1; |
246 | 0 | if (iter->current->next == NULL) |
247 | 0 | return -1; |
248 | | |
249 | 0 | iter->current = iter->current->next; |
250 | |
|
251 | 0 | return 0; |
252 | 0 | } |
253 | | |
254 | | static int |
255 | | iter_std_prev(fz_context *ctx, fz_outline_iterator *iter_) |
256 | 0 | { |
257 | 0 | fz_outline_iter_std *iter = (fz_outline_iter_std *)iter_; |
258 | 0 | fz_outline *first; |
259 | |
|
260 | 0 | if (iter->current == NULL) |
261 | 0 | return -1; |
262 | 0 | first = iter->down_len == 0 ? iter->outline : iter->down_array[iter->down_len-1]; |
263 | 0 | if (iter->current == first) |
264 | 0 | return -1; |
265 | | |
266 | 0 | while (first->next != iter->current) |
267 | 0 | first = first->next; |
268 | |
|
269 | 0 | iter->current = first; |
270 | |
|
271 | 0 | return 0; |
272 | 0 | } |
273 | | |
274 | | static fz_outline_item * |
275 | | iter_std_item(fz_context *ctx, fz_outline_iterator *iter_) |
276 | 0 | { |
277 | 0 | fz_outline_iter_std *iter = (fz_outline_iter_std *)iter_; |
278 | |
|
279 | 0 | if (iter->current == NULL) |
280 | 0 | return NULL; |
281 | | |
282 | 0 | iter->item.is_open = iter->current->is_open; |
283 | 0 | iter->item.title = iter->current->title; |
284 | 0 | iter->item.uri = iter->current->uri; |
285 | 0 | iter->item.r = iter->current->r / 255.0f; |
286 | 0 | iter->item.g = iter->current->g / 255.0f; |
287 | 0 | iter->item.b = iter->current->b / 255.0f; |
288 | 0 | iter->item.flags = iter->current->flags; |
289 | |
|
290 | 0 | return &iter->item; |
291 | 0 | } |
292 | | |
293 | | static void |
294 | | iter_std_drop(fz_context *ctx, fz_outline_iterator *iter_) |
295 | 0 | { |
296 | 0 | fz_outline_iter_std *iter = (fz_outline_iter_std *)iter_; |
297 | |
|
298 | 0 | if (iter == NULL) |
299 | 0 | return; |
300 | | |
301 | 0 | fz_drop_outline(ctx, iter->outline); |
302 | 0 | fz_free(ctx, iter->down_array); |
303 | 0 | } |
304 | | |
305 | | fz_outline_iterator *fz_outline_iterator_from_outline(fz_context *ctx, fz_outline *outline) |
306 | 0 | { |
307 | 0 | fz_outline_iter_std *iter; |
308 | |
|
309 | 0 | fz_try(ctx) |
310 | 0 | { |
311 | 0 | iter = fz_malloc_struct(ctx, fz_outline_iter_std); |
312 | 0 | iter->super.down = iter_std_down; |
313 | 0 | iter->super.up = iter_std_up; |
314 | 0 | iter->super.next = iter_std_next; |
315 | 0 | iter->super.prev = iter_std_prev; |
316 | 0 | iter->super.item = iter_std_item; |
317 | 0 | iter->super.drop = iter_std_drop; |
318 | 0 | iter->outline = outline; |
319 | 0 | iter->current = outline; |
320 | 0 | } |
321 | 0 | fz_catch(ctx) |
322 | 0 | { |
323 | 0 | fz_drop_outline(ctx, outline); |
324 | 0 | fz_rethrow(ctx); |
325 | 0 | } |
326 | | |
327 | 0 | return &iter->super; |
328 | 0 | } |