/src/mupdf/source/svg/svg-doc.c
Line | Count | Source |
1 | | // Copyright (C) 2004-2024 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 | | #include "svg-imp.h" |
25 | | |
26 | | typedef struct |
27 | | { |
28 | | fz_page super; |
29 | | svg_document *doc; |
30 | | } svg_page; |
31 | | |
32 | | static void |
33 | | svg_drop_document(fz_context *ctx, fz_document *doc_) |
34 | 0 | { |
35 | 0 | svg_document *doc = (svg_document*)doc_; |
36 | 0 | fz_drop_tree(ctx, doc->idmap, NULL); |
37 | 0 | fz_drop_xml(ctx, doc->xml); |
38 | 0 | } |
39 | | |
40 | | static int |
41 | | svg_count_pages(fz_context *ctx, fz_document *doc_, int chapter) |
42 | 0 | { |
43 | 0 | return 1; |
44 | 0 | } |
45 | | |
46 | | static fz_rect |
47 | | svg_bound_page(fz_context *ctx, fz_page *page_, fz_box_type box) |
48 | 0 | { |
49 | 0 | svg_page *page = (svg_page*)page_; |
50 | 0 | svg_document *doc = page->doc; |
51 | |
|
52 | 0 | svg_parse_document_bounds(ctx, doc, doc->root); |
53 | |
|
54 | 0 | return fz_make_rect(0, 0, doc->width, doc->height); |
55 | 0 | } |
56 | | |
57 | | static void |
58 | | svg_run_page(fz_context *ctx, fz_page *page_, fz_device *dev, fz_matrix ctm, fz_cookie *cookie) |
59 | 0 | { |
60 | 0 | svg_page *page = (svg_page*)page_; |
61 | 0 | svg_document *doc = page->doc; |
62 | 0 | svg_run_document(ctx, doc, doc->root, dev, ctm); |
63 | 0 | } |
64 | | |
65 | | static void |
66 | | svg_drop_page(fz_context *ctx, fz_page *page_) |
67 | 0 | { |
68 | | /* nothing */ |
69 | 0 | } |
70 | | |
71 | | static fz_page * |
72 | | svg_load_page(fz_context *ctx, fz_document *doc_, int chapter, int number) |
73 | 0 | { |
74 | 0 | svg_document *doc = (svg_document*)doc_; |
75 | 0 | svg_page *page; |
76 | |
|
77 | 0 | if (number != 0) |
78 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "cannot find page %d", number); |
79 | | |
80 | 0 | page = fz_new_derived_page(ctx, svg_page, doc_); |
81 | 0 | page->super.bound_page = svg_bound_page; |
82 | 0 | page->super.run_page_contents = svg_run_page; |
83 | 0 | page->super.drop_page = svg_drop_page; |
84 | 0 | page->doc = doc; |
85 | |
|
86 | 0 | return (fz_page*)page; |
87 | 0 | } |
88 | | |
89 | | static void |
90 | | svg_build_id_map(fz_context *ctx, svg_document *doc, fz_xml *root) |
91 | 0 | { |
92 | 0 | fz_xml *node; |
93 | |
|
94 | 0 | char *id_att = fz_xml_att(root, "id"); |
95 | 0 | if (id_att) |
96 | 0 | doc->idmap = fz_tree_insert(ctx, doc->idmap, id_att, root); |
97 | |
|
98 | 0 | for (node = fz_xml_down(root); node; node = fz_xml_next(node)) |
99 | 0 | svg_build_id_map(ctx, doc, node); |
100 | 0 | } |
101 | | |
102 | | static int |
103 | | svg_lookup_metadata(fz_context *ctx, fz_document *doc_, const char *key, char *buf, size_t size) |
104 | 0 | { |
105 | 0 | if (!strcmp(key, FZ_META_FORMAT)) |
106 | 0 | return 1 + (int)fz_strlcpy(buf, "SVG", size); |
107 | 0 | return -1; |
108 | 0 | } |
109 | | |
110 | | static fz_document * |
111 | | svg_open_document_with_xml(fz_context *ctx, fz_xml_doc *xmldoc, fz_xml *xml, const char *base_uri, fz_archive *zip) |
112 | 0 | { |
113 | 0 | svg_document *doc; |
114 | |
|
115 | 0 | doc = fz_new_derived_document(ctx, svg_document); |
116 | 0 | doc->super.drop_document = svg_drop_document; |
117 | 0 | doc->super.count_pages = svg_count_pages; |
118 | 0 | doc->super.load_page = svg_load_page; |
119 | 0 | doc->super.lookup_metadata = svg_lookup_metadata; |
120 | |
|
121 | 0 | doc->idmap = NULL; |
122 | 0 | if (base_uri) |
123 | 0 | fz_strlcpy(doc->base_uri, base_uri, sizeof doc->base_uri); |
124 | 0 | doc->xml = NULL; |
125 | 0 | doc->root = xml; |
126 | 0 | doc->zip = zip; |
127 | |
|
128 | 0 | fz_try(ctx) |
129 | 0 | { |
130 | 0 | if (xmldoc) |
131 | 0 | svg_build_id_map(ctx, doc, fz_xml_root(xmldoc)); |
132 | 0 | else |
133 | 0 | svg_build_id_map(ctx, doc, doc->root); |
134 | 0 | } |
135 | 0 | fz_catch(ctx) |
136 | 0 | { |
137 | 0 | fz_drop_document(ctx, &doc->super); |
138 | 0 | fz_rethrow(ctx); |
139 | 0 | } |
140 | | |
141 | 0 | return (fz_document*)doc; |
142 | 0 | } |
143 | | |
144 | | static fz_document * |
145 | | svg_open_document_with_buffer(fz_context *ctx, fz_buffer *buf, const char *base_uri, fz_archive *zip) |
146 | 0 | { |
147 | 0 | svg_document *doc; |
148 | |
|
149 | 0 | doc = fz_new_derived_document(ctx, svg_document); |
150 | 0 | doc->super.drop_document = svg_drop_document; |
151 | 0 | doc->super.count_pages = svg_count_pages; |
152 | 0 | doc->super.load_page = svg_load_page; |
153 | 0 | doc->super.lookup_metadata = svg_lookup_metadata; |
154 | |
|
155 | 0 | doc->idmap = NULL; |
156 | 0 | if (base_uri) |
157 | 0 | fz_strlcpy(doc->base_uri, base_uri, sizeof doc->base_uri); |
158 | 0 | doc->zip = zip; |
159 | |
|
160 | 0 | fz_try(ctx) |
161 | 0 | { |
162 | 0 | doc->xml = fz_parse_xml(ctx, buf, 0); |
163 | 0 | doc->root = fz_xml_root(doc->xml); |
164 | 0 | svg_build_id_map(ctx, doc, doc->root); |
165 | 0 | } |
166 | 0 | fz_catch(ctx) |
167 | 0 | { |
168 | 0 | fz_drop_document(ctx, &doc->super); |
169 | 0 | fz_rethrow(ctx); |
170 | 0 | } |
171 | | |
172 | 0 | return (fz_document*)doc; |
173 | 0 | } |
174 | | |
175 | | static fz_document * |
176 | | svg_open_document(fz_context *ctx, const fz_document_handler *handler, fz_stream *file, fz_stream *accel, fz_archive *zip, void *state) |
177 | 0 | { |
178 | 0 | fz_buffer *buf = fz_read_all(ctx, file, 0); |
179 | 0 | fz_document *doc = NULL; |
180 | |
|
181 | 0 | fz_try(ctx) |
182 | 0 | doc = svg_open_document_with_buffer(ctx, buf, NULL, NULL); |
183 | 0 | fz_always(ctx) |
184 | 0 | fz_drop_buffer(ctx, buf); |
185 | 0 | fz_catch(ctx) |
186 | 0 | fz_rethrow(ctx); |
187 | | |
188 | 0 | return doc; |
189 | 0 | } |
190 | | |
191 | | fz_display_list * |
192 | | fz_new_display_list_from_svg(fz_context *ctx, fz_buffer *buf, const char *base_uri, fz_archive *zip, float *w, float *h) |
193 | 0 | { |
194 | 0 | fz_document *doc; |
195 | 0 | fz_display_list *list = NULL; |
196 | |
|
197 | 0 | doc = svg_open_document_with_buffer(ctx, buf, base_uri, zip); |
198 | 0 | fz_try(ctx) |
199 | 0 | { |
200 | 0 | list = fz_new_display_list_from_page_number(ctx, doc, 0); |
201 | 0 | *w = ((svg_document*)doc)->width; |
202 | 0 | *h = ((svg_document*)doc)->height; |
203 | 0 | } |
204 | 0 | fz_always(ctx) |
205 | 0 | fz_drop_document(ctx, doc); |
206 | 0 | fz_catch(ctx) |
207 | 0 | fz_rethrow(ctx); |
208 | | |
209 | 0 | return list; |
210 | 0 | } |
211 | | |
212 | | fz_display_list * |
213 | | fz_new_display_list_from_svg_xml(fz_context *ctx, fz_xml_doc *xmldoc, fz_xml *xml, const char *base_uri, fz_archive *zip, float *w, float *h) |
214 | 0 | { |
215 | 0 | fz_document *doc; |
216 | 0 | fz_display_list *list = NULL; |
217 | |
|
218 | 0 | doc = svg_open_document_with_xml(ctx, xmldoc, xml, base_uri, zip); |
219 | 0 | fz_try(ctx) |
220 | 0 | { |
221 | 0 | list = fz_new_display_list_from_page_number(ctx, doc, 0); |
222 | 0 | *w = ((svg_document*)doc)->width; |
223 | 0 | *h = ((svg_document*)doc)->height; |
224 | 0 | } |
225 | 0 | fz_always(ctx) |
226 | 0 | fz_drop_document(ctx, doc); |
227 | 0 | fz_catch(ctx) |
228 | 0 | fz_rethrow(ctx); |
229 | | |
230 | 0 | return list; |
231 | 0 | } |
232 | | |
233 | | fz_image * |
234 | | fz_new_image_from_svg(fz_context *ctx, fz_buffer *buf, const char *base_uri, fz_archive *zip) |
235 | 0 | { |
236 | 0 | fz_display_list *list; |
237 | 0 | fz_image *image = NULL; |
238 | 0 | float w, h; |
239 | |
|
240 | 0 | list = fz_new_display_list_from_svg(ctx, buf, base_uri, zip, &w, &h); |
241 | 0 | fz_try(ctx) |
242 | 0 | image = fz_new_image_from_display_list(ctx, w, h, list); |
243 | 0 | fz_always(ctx) |
244 | 0 | fz_drop_display_list(ctx, list); |
245 | 0 | fz_catch(ctx) |
246 | 0 | fz_rethrow(ctx); |
247 | 0 | return image; |
248 | 0 | } |
249 | | |
250 | | fz_image * |
251 | | fz_new_image_from_svg_xml(fz_context *ctx, fz_xml_doc *xmldoc, fz_xml *xml, const char *base_uri, fz_archive *zip) |
252 | 0 | { |
253 | 0 | fz_display_list *list; |
254 | 0 | fz_image *image = NULL; |
255 | 0 | float w, h; |
256 | |
|
257 | 0 | list = fz_new_display_list_from_svg_xml(ctx, xmldoc, xml, base_uri, zip, &w, &h); |
258 | 0 | fz_try(ctx) |
259 | 0 | image = fz_new_image_from_display_list(ctx, w, h, list); |
260 | 0 | fz_always(ctx) |
261 | 0 | fz_drop_display_list(ctx, list); |
262 | 0 | fz_catch(ctx) |
263 | 0 | fz_rethrow(ctx); |
264 | 0 | return image; |
265 | 0 | } |
266 | | |
267 | | static const char *svg_extensions[] = |
268 | | { |
269 | | "svg", |
270 | | NULL |
271 | | }; |
272 | | |
273 | | static const char *svg_mimetypes[] = |
274 | | { |
275 | | "image/svg+xml", |
276 | | NULL |
277 | | }; |
278 | | |
279 | | static int |
280 | | svg_recognize_doc_content(fz_context *ctx, const fz_document_handler *handler, fz_stream *stm, fz_archive *dir, void **state, fz_document_recognize_state_free_fn **free_state) |
281 | 12 | { |
282 | | // A standalone SVG document is an XML document with an <svg> root element. |
283 | | // |
284 | | // Assume the document is ASCII or UTF-8. |
285 | | // |
286 | | // Parse the start of the file using a simplified XML parser, skipping |
287 | | // processing instructions and comments, and stopping at the first |
288 | | // element. |
289 | | // |
290 | | // Return failure on anything unexpected, or if the first element is not SVG. |
291 | | |
292 | 12 | int c; |
293 | | |
294 | 12 | if (state) |
295 | 12 | *state = NULL; |
296 | 12 | if (free_state) |
297 | 12 | *free_state = NULL; |
298 | | |
299 | 12 | if (stm == NULL) |
300 | 0 | return 0; |
301 | | |
302 | 12 | parse_text: |
303 | | // Skip whitespace until "<" |
304 | 12 | c = fz_read_byte(ctx, stm); |
305 | 12 | while (c == ' ' || c == '\r' || c == '\n' || c == '\t') |
306 | 0 | c = fz_read_byte(ctx, stm); |
307 | 12 | if (c == '<') |
308 | 0 | goto parse_element; |
309 | 12 | return 0; |
310 | | |
311 | 0 | parse_element: |
312 | | // Either "<?...>" or "<!...>" or "<svg" or not an SVG document. |
313 | 0 | c = fz_read_byte(ctx, stm); |
314 | 0 | if (c == '!' || c == '?') |
315 | 0 | goto parse_comment; |
316 | 0 | if (c != 's') |
317 | 0 | return 0; |
318 | 0 | c = fz_read_byte(ctx, stm); |
319 | 0 | if (c != 'v') |
320 | 0 | return 0; |
321 | 0 | c = fz_read_byte(ctx, stm); |
322 | 0 | if (c != 'g') |
323 | 0 | return 0; |
324 | 0 | return 100; |
325 | | |
326 | 0 | parse_comment: |
327 | | // Skip everything after "<?" or "<!" until ">" |
328 | 0 | c = fz_read_byte(ctx, stm); |
329 | 0 | while (c != EOF && c != '>') |
330 | 0 | c = fz_read_byte(ctx, stm); |
331 | 0 | if (c == '>') |
332 | 0 | goto parse_text; |
333 | 0 | return 0; |
334 | 0 | } |
335 | | |
336 | | fz_document_handler svg_document_handler = |
337 | | { |
338 | | NULL, |
339 | | svg_open_document, |
340 | | svg_extensions, |
341 | | svg_mimetypes, |
342 | | svg_recognize_doc_content |
343 | | }; |