Coverage Report

Created: 2025-01-28 06:17

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