Coverage Report

Created: 2026-03-31 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
};