1 /* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
2 /* cairo - a vector graphics library with display and print output
3  *
4  * Copyright © 2016 Adrian Johnson
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it either under the terms of the GNU Lesser General Public
8  * License version 2.1 as published by the Free Software Foundation
9  * (the "LGPL") or, at your option, under the terms of the Mozilla
10  * Public License Version 1.1 (the "MPL"). If you do not alter this
11  * notice, a recipient may use your version of this file under either
12  * the MPL or the LGPL.
13  *
14  * You should have received a copy of the LGPL along with this library
15  * in the file COPYING-LGPL-2.1; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
17  * You should have received a copy of the MPL along with this library
18  * in the file COPYING-MPL-1.1
19  *
20  * The contents of this file are subject to the Mozilla Public License
21  * Version 1.1 (the "License"); you may not use this file except in
22  * compliance with the License. You may obtain a copy of the License at
23  * http://www.mozilla.org/MPL/
24  *
25  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
26  * OF ANY KIND, either express or implied. See the LGPL or the MPL for
27  * the specific language governing rights and limitations.
28  *
29  * The Original Code is the cairo graphics library.
30  *
31  * The Initial Developer of the Original Code is Adrian Johnson.
32  *
33  * Contributor(s):
34  *	Adrian Johnson <ajohnson@redneon.com>
35  */
36 
37 #include "cairoint.h"
38 
39 #include "cairo-array-private.h"
40 #include "cairo-list-inline.h"
41 #include "cairo-tag-attributes-private.h"
42 #include "cairo-tag-stack-private.h"
43 
44 #include <string.h>
45 
46 typedef enum {
47     ATTRIBUTE_BOOL,  /* Either true/false or 1/0 may be used. */
48     ATTRIBUTE_INT,
49     ATTRIBUTE_FLOAT, /* Decimal separator is in current locale. */
50     ATTRIBUTE_STRING, /* Enclose in single quotes. String escapes:
51                        *   \'  - single quote
52                        *   \\  - backslash
53                        */
54 } attribute_type_t;
55 
56 typedef struct _attribute_spec {
57     const char *name;
58     attribute_type_t type;
59     int array_size; /* 0 = scalar, -1 = variable size array */
60 } attribute_spec_t;
61 
62 /*
63  * name [required] Unique name of this destination (UTF-8)
64  * x    [optional] x coordinate of destination on page. Default is x coord of
65  *                 extents of operations enclosed by the dest begin/end tags.
66  * y    [optional] y coordinate of destination on page. Default is y coord of
67  *                 extents of operations enclosed by the dest begin/end tags.
68  * internal [optional] If true, the name may be optimized out of the PDF where
69  *                     possible. Default false.
70  */
71 static attribute_spec_t _dest_attrib_spec[] = {
72     { "name",     ATTRIBUTE_STRING },
73     { "x",        ATTRIBUTE_FLOAT },
74     { "y",        ATTRIBUTE_FLOAT },
75     { "internal", ATTRIBUTE_BOOL },
76     { NULL }
77 };
78 
79 /*
80  * rect [optional] One or more rectangles to define link region. Default
81  *                 is the extents of the operations enclosed by the link begin/end tags.
82  *                 Each rectangle is specified by four array elements: x, y, width, height.
83  *                 ie the array size must be a multiple of four.
84  *
85  * Internal Links
86  * --------------
87  * either:
88  *   dest - name of dest tag in the PDF file to link to (UTF8)
89  * or
90  *   page - Page number in the PDF file to link to
91  *   pos  - [optional] Position of destination on page. Default is 0,0.
92  *
93  * URI Links
94  * ---------
95  * uri [required] Uniform resource identifier (ASCII).
96 
97  * File Links
98  * ----------
99  * file - [required] File name of PDF file to link to.
100  *   either:
101  *     dest - name of dest tag in the PDF file to link to (UTF8)
102  *   or
103  *     page - Page number in the PDF file to link to
104  *     pos  - [optional] Position of destination on page. Default is 0,0.
105  */
106 static attribute_spec_t _link_attrib_spec[] =
107 {
108     { "rect", ATTRIBUTE_FLOAT, -1 },
109     { "dest", ATTRIBUTE_STRING },
110     { "uri",  ATTRIBUTE_STRING },
111     { "file", ATTRIBUTE_STRING },
112     { "page", ATTRIBUTE_INT },
113     { "pos",  ATTRIBUTE_FLOAT, 2 },
114     { NULL }
115 };
116 
117 /*
118  * Required:
119  *   Columns - width of the image in pixels.
120  *   Rows    - height of the image in scan lines.
121  *
122  * Optional:
123  *   K - An integer identifying the encoding scheme used. < 0 is 2 dimensional
124  *       Group 4, = 0 is Group3 1 dimensional, > 0 is mixed 1 and 2 dimensional
125  *       encoding. Default: 0.
126  *   EndOfLine  - If true end-of-line bit patterns are present. Default: false.
127  *   EncodedByteAlign - If true the end of line is padded with 0 bits so the next
128  *                      line begins on a byte boundary. Default: false.
129  *   EndOfBlock - If true the data contains an end-of-block pattern. Default: true.
130  *   BlackIs1   - If true 1 bits are black pixels. Default: false.
131  *   DamagedRowsBeforeError - Number of damages rows tolerated before an error
132  *                            occurs. Default: 0.
133  */
134 static attribute_spec_t _ccitt_params_spec[] =
135 {
136     { "Columns",                ATTRIBUTE_INT },
137     { "Rows",                   ATTRIBUTE_INT },
138     { "K",                      ATTRIBUTE_INT },
139     { "EndOfLine",              ATTRIBUTE_BOOL },
140     { "EncodedByteAlign",       ATTRIBUTE_BOOL },
141     { "EndOfBlock",             ATTRIBUTE_BOOL },
142     { "BlackIs1",               ATTRIBUTE_BOOL },
143     { "DamagedRowsBeforeError", ATTRIBUTE_INT },
144     { NULL }
145 };
146 
147 /*
148  * bbox - Bounding box of EPS file. The format is [ llx lly urx ury ]
149  *          llx - lower left x xoordinate
150  *          lly - lower left y xoordinate
151  *          urx - upper right x xoordinate
152  *          ury - upper right y xoordinate
153  *        all coordinates are in PostScript coordinates.
154  */
155 static attribute_spec_t _eps_params_spec[] =
156 {
157     { "bbox", ATTRIBUTE_FLOAT, 4 },
158     { NULL }
159 };
160 
161 typedef union {
162     cairo_bool_t b;
163     int i;
164     double f;
165     char *s;
166 } attrib_val_t;
167 
168 typedef struct _attribute {
169     char *name;
170     attribute_type_t type;
171     int array_len; /* 0 = scalar */
172     attrib_val_t scalar;
173     cairo_array_t array; /* array of attrib_val_t */
174     cairo_list_t link;
175 } attribute_t;
176 
177 static const char *
skip_space(const char * p)178 skip_space (const char *p)
179 {
180     while (_cairo_isspace (*p))
181 	p++;
182 
183     return p;
184 }
185 
186 static const char *
parse_bool(const char * p,cairo_bool_t * b)187 parse_bool (const char *p, cairo_bool_t *b)
188 {
189     if (*p == '1') {
190 	*b = TRUE;
191 	return p + 1;
192     } else if (*p == '0') {
193 	*b = FALSE;
194 	return p + 1;
195     } else if (strcmp (p, "true") == 0) {
196 	*b = TRUE;
197 	return p + 4;
198     } else if (strcmp (p, "false") == 0) {
199 	*b = FALSE;
200 	return p + 5;
201     }
202 
203     return NULL;
204 }
205 
206 static const char *
parse_int(const char * p,int * i)207 parse_int (const char *p, int *i)
208 {
209     int n;
210 
211     if (sscanf(p, "%d%n", i, &n) > 0)
212 	return p + n;
213 
214     return NULL;
215 }
216 
217 static const char *
parse_float(const char * p,double * d)218 parse_float (const char *p, double *d)
219 {
220     int n;
221     const char *start = p;
222     cairo_bool_t has_decimal_point = FALSE;
223 
224     while (*p) {
225 	if (*p == '.' || *p == ']' || _cairo_isspace (*p))
226 	    break;
227 	p++;
228     }
229 
230     if (*p == '.')
231 	has_decimal_point = TRUE;
232 
233     if (has_decimal_point) {
234 	char *end;
235 	*d = _cairo_strtod (start, &end);
236 	if (end && end != start)
237 	    return end;
238 
239     } else {
240 	if (sscanf(start, "%lf%n", d, &n) > 0)
241 	    return start + n;
242     }
243 
244     return NULL;
245 }
246 
247 static const char *
decode_string(const char * p,int * len,char * s)248 decode_string (const char *p, int *len, char *s)
249 {
250     if (*p != '\'')
251 	return NULL;
252 
253     p++;
254     if (! *p)
255 	return NULL;
256 
257     *len = 0;
258     while (*p) {
259 	if (*p == '\\') {
260 	    p++;
261 	    if (*p) {
262 		if (s)
263 		    *s++ = *p;
264 		p++;
265 		(*len)++;
266 	    }
267 	} else if (*p == '\'') {
268 	    return p + 1;
269 	} else {
270 	    if (s)
271 		*s++ = *p;
272 	    p++;
273 	    (*len)++;
274 	}
275     }
276 
277     return NULL;
278 }
279 
280 static const char *
parse_string(const char * p,char ** s)281 parse_string (const char *p, char **s)
282 {
283     const char *end;
284     int len;
285 
286     end = decode_string (p, &len, NULL);
287     if (!end)
288 	return NULL;
289 
290     *s = _cairo_malloc (len + 1);
291     decode_string (p, &len, *s);
292     (*s)[len] = 0;
293 
294     return end;
295 }
296 
297 static const char *
parse_scalar(const char * p,attribute_type_t type,attrib_val_t * scalar)298 parse_scalar (const char *p, attribute_type_t type, attrib_val_t *scalar)
299 {
300     switch (type) {
301 	case ATTRIBUTE_BOOL:
302 	    return parse_bool (p, &scalar->b);
303 	case ATTRIBUTE_INT:
304 	    return parse_int (p, &scalar->i);
305 	case ATTRIBUTE_FLOAT:
306 	    return parse_float (p, &scalar->f);
307 	case ATTRIBUTE_STRING:
308 	    return parse_string (p, &scalar->s);
309     }
310 
311     return NULL;
312 }
313 
314 static cairo_int_status_t
parse_array(const char * attributes,const char * p,attribute_type_t type,cairo_array_t * array,const char ** end)315 parse_array (const char *attributes, const char *p, attribute_type_t type, cairo_array_t *array, const char **end)
316 {
317     attrib_val_t val;
318     cairo_int_status_t status;
319 
320     p = skip_space (p);
321     if (! *p)
322 	goto error;
323 
324     if (*p++ != '[')
325 	goto error;
326 
327     while (TRUE) {
328 	p = skip_space (p);
329 	if (! *p)
330 	    goto error;
331 
332 	if (*p == ']') {
333 	    *end = p + 1;
334 	    return CAIRO_INT_STATUS_SUCCESS;
335 	}
336 
337 	p = parse_scalar (p, type, &val);
338 	if (!p)
339 	    goto error;
340 
341 	status = _cairo_array_append (array, &val);
342 	if (unlikely (status))
343 	    return status;
344     }
345 
346   error:
347     return _cairo_tag_error (
348 	"while parsing attributes: \"%s\". Error parsing array", attributes);
349 }
350 
351 static cairo_int_status_t
parse_name(const char * attributes,const char * p,const char ** end,char ** s)352 parse_name (const char *attributes, const char *p, const char **end, char **s)
353 {
354     const char *p2;
355     char *name;
356     int len;
357 
358     if (! _cairo_isalpha (*p))
359 	return _cairo_tag_error (
360 	    "while parsing attributes: \"%s\". Error parsing name."
361 	    " \"%s\" does not start with an alphabetic character",
362 	    attributes, p);
363 
364     p2 = p;
365     while (_cairo_isalpha (*p2) || _cairo_isdigit (*p2))
366 	p2++;
367 
368     len = p2 - p;
369     name = _cairo_malloc (len + 1);
370     if (unlikely (name == NULL))
371 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
372 
373     memcpy (name, p, len);
374     name[len] = 0;
375     *s = name;
376     *end = p2;
377 
378     return CAIRO_INT_STATUS_SUCCESS;
379 }
380 
381 static cairo_int_status_t
parse_attributes(const char * attributes,attribute_spec_t * attrib_def,cairo_list_t * list)382 parse_attributes (const char *attributes, attribute_spec_t *attrib_def, cairo_list_t *list)
383 {
384     attribute_spec_t *def;
385     attribute_t *attrib;
386     char *name = NULL;
387     cairo_int_status_t status;
388     const char *p = attributes;
389 
390     if (! p)
391 	return CAIRO_INT_STATUS_SUCCESS;
392 
393     while (*p) {
394 	p = skip_space (p);
395 	if (! *p)
396 	    break;
397 
398 	status = parse_name (attributes, p, &p, &name);
399 	if (status)
400 	    return status;
401 
402 	def = attrib_def;
403 	while (def->name) {
404 	    if (strcmp (name, def->name) == 0)
405 		break;
406 	    def++;
407 	}
408 
409 	if (! def->name) {
410 	    status = _cairo_tag_error (
411 		"while parsing attributes: \"%s\". Unknown attribute name \"%s\"",
412 		attributes, name);
413 	    goto fail1;
414 	}
415 
416 	attrib = calloc (1, sizeof (attribute_t));
417 	if (unlikely (attrib == NULL)) {
418 	    status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
419 	    goto fail1;
420 	}
421 
422 	attrib->name = name;
423 	attrib->type = def->type;
424 	_cairo_array_init (&attrib->array, sizeof(attrib_val_t));
425 
426 	p = skip_space (p);
427 	if (def->type == ATTRIBUTE_BOOL && *p != '=') {
428 	    attrib->scalar.b = TRUE;
429 	} else {
430 	    if (*p++ != '=') {
431 		status = _cairo_tag_error (
432 		    "while parsing attributes: \"%s\". Expected '=' after \"%s\"",
433 		    attributes, name);
434 		goto fail2;
435 	    }
436 
437 	    if (def->array_size == 0) {
438 		const char *s = p;
439 		p = parse_scalar (p, def->type, &attrib->scalar);
440 		if (!p) {
441 		    status = _cairo_tag_error (
442 			"while parsing attributes: \"%s\". Error parsing \"%s\"",
443 			attributes, s);
444 		    goto fail2;
445 		}
446 
447 		attrib->array_len = 0;
448 	    } else {
449 		status = parse_array (attributes, p, def->type, &attrib->array, &p);
450 		if (unlikely (status))
451 		    goto fail2;
452 
453 		attrib->array_len = _cairo_array_num_elements (&attrib->array);
454 		if (def->array_size > 0 && attrib->array_len != def->array_size) {
455 		    status = _cairo_tag_error (
456 			"while parsing attributes: \"%s\". Expected %d elements in array. Found %d",
457 			attributes, def->array_size, attrib->array_len);
458 		    goto fail2;
459 		}
460 	    }
461 	}
462 
463 	cairo_list_add_tail (&attrib->link, list);
464     }
465 
466     return CAIRO_INT_STATUS_SUCCESS;
467 
468   fail2:
469     _cairo_array_fini (&attrib->array);
470     if (attrib->type == ATTRIBUTE_STRING)
471 	free (attrib->scalar.s);
472     free (attrib);
473   fail1:
474     free (name);
475 
476     return status;
477 }
478 
479 static void
free_attributes_list(cairo_list_t * list)480 free_attributes_list (cairo_list_t *list)
481 {
482     attribute_t *attr, *next;
483 
484     cairo_list_foreach_entry_safe (attr, next, attribute_t, list, link)
485     {
486 	cairo_list_del (&attr->link);
487 	free (attr->name);
488 	_cairo_array_fini (&attr->array);
489 	if (attr->type == ATTRIBUTE_STRING)
490 	    free (attr->scalar.s);
491 	free (attr);
492     }
493 }
494 
495 cairo_int_status_t
_cairo_tag_parse_link_attributes(const char * attributes,cairo_link_attrs_t * link_attrs)496 _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *link_attrs)
497 {
498     cairo_list_t list;
499     cairo_int_status_t status;
500     attribute_t *attr;
501     attrib_val_t val;
502     cairo_bool_t has_rect = FALSE;
503     cairo_bool_t invalid_combination = FALSE;
504 
505     cairo_list_init (&list);
506     status = parse_attributes (attributes, _link_attrib_spec, &list);
507     if (unlikely (status))
508 	return status;
509 
510     memset (link_attrs, 0, sizeof (cairo_link_attrs_t));
511     _cairo_array_init (&link_attrs->rects, sizeof (cairo_rectangle_t));
512 
513     cairo_list_foreach_entry (attr, attribute_t, &list, link) {
514 	if (strcmp (attr->name, "dest") == 0) {
515 	    link_attrs->dest = strdup (attr->scalar.s);
516 
517 	} else if (strcmp (attr->name, "page") == 0) {
518 	    link_attrs->page = attr->scalar.i;
519 	    if (link_attrs->page < 1) {
520 		status = _cairo_tag_error ("Link attributes: \"%s\" page must be >= 1", attributes);
521 		goto cleanup;
522 	    }
523 
524 	} else if (strcmp (attr->name, "pos") == 0) {
525 	    _cairo_array_copy_element (&attr->array, 0, &val);
526 	    link_attrs->pos.x = val.f;
527 	    _cairo_array_copy_element (&attr->array, 1, &val);
528 	    link_attrs->pos.y = val.f;
529 	    link_attrs->has_pos = TRUE;
530 
531 	} else if (strcmp (attr->name, "uri") == 0) {
532 	    link_attrs->uri = strdup (attr->scalar.s);
533 
534 	} else if (strcmp (attr->name, "file") == 0) {
535 	    link_attrs->file = strdup (attr->scalar.s);
536 
537 	} else if (strcmp (attr->name, "rect") == 0) {
538 	    cairo_rectangle_t rect;
539 	    int i;
540 	    int num_elem = _cairo_array_num_elements (&attr->array);
541 	    if (num_elem == 0 || num_elem % 4 != 0) {
542 		status = _cairo_tag_error ("Link attributes: \"%s\" rect array size must be multiple of 4",
543 					   attributes);
544 		goto cleanup;
545 	    }
546 
547 	    for (i = 0; i < num_elem; i += 4) {
548 		_cairo_array_copy_element (&attr->array, i, &val);
549 		rect.x = val.f;
550 		_cairo_array_copy_element (&attr->array, i+1, &val);
551 		rect.y = val.f;
552 		_cairo_array_copy_element (&attr->array, i+2, &val);
553 		rect.width = val.f;
554 		_cairo_array_copy_element (&attr->array, i+3, &val);
555 		rect.height = val.f;
556 		status = _cairo_array_append (&link_attrs->rects, &rect);
557 		if (unlikely (status))
558 		    goto cleanup;
559 	    }
560 	    has_rect = TRUE;
561 	}
562     }
563 
564     if (link_attrs->uri) {
565 	link_attrs->link_type = TAG_LINK_URI;
566 	if (link_attrs->dest || link_attrs->page || link_attrs->has_pos || link_attrs->file)
567 	    invalid_combination = TRUE;
568 
569     } else if (link_attrs->file) {
570 	link_attrs->link_type = TAG_LINK_FILE;
571 	if (link_attrs->uri)
572 	    invalid_combination = TRUE;
573 	else if (link_attrs->dest && (link_attrs->page || link_attrs->has_pos))
574 	    invalid_combination = TRUE;
575 
576     } else if (link_attrs->dest) {
577 	link_attrs->link_type = TAG_LINK_DEST;
578 	if (link_attrs->uri || link_attrs->page || link_attrs->has_pos)
579 	    invalid_combination = TRUE;
580 
581     } else if (link_attrs->page) {
582 	link_attrs->link_type = TAG_LINK_DEST;
583 	if (link_attrs->uri || link_attrs->dest)
584 	    invalid_combination = TRUE;
585 
586     } else {
587 	link_attrs->link_type = TAG_LINK_EMPTY;
588 	if (link_attrs->has_pos)
589 	    invalid_combination = TRUE;
590     }
591 
592     if (invalid_combination) {
593 	status = _cairo_tag_error (
594 	    "Link attributes: \"%s\" invalid combination of attributes", attributes);
595     }
596 
597   cleanup:
598     free_attributes_list (&list);
599     if (unlikely (status)) {
600 	free (link_attrs->dest);
601 	free (link_attrs->uri);
602 	free (link_attrs->file);
603 	_cairo_array_fini (&link_attrs->rects);
604     }
605 
606     return status;
607 }
608 
609 cairo_int_status_t
_cairo_tag_parse_dest_attributes(const char * attributes,cairo_dest_attrs_t * dest_attrs)610 _cairo_tag_parse_dest_attributes (const char *attributes, cairo_dest_attrs_t *dest_attrs)
611 {
612     cairo_list_t list;
613     cairo_int_status_t status;
614     attribute_t *attr;
615 
616     memset (dest_attrs, 0, sizeof (cairo_dest_attrs_t));
617     cairo_list_init (&list);
618     status = parse_attributes (attributes, _dest_attrib_spec, &list);
619     if (unlikely (status))
620 	goto cleanup;
621 
622     cairo_list_foreach_entry (attr, attribute_t, &list, link)
623     {
624 	if (strcmp (attr->name, "name") == 0) {
625 	    dest_attrs->name = strdup (attr->scalar.s);
626 	} else if (strcmp (attr->name, "x") == 0) {
627 	    dest_attrs->x = attr->scalar.f;
628 	    dest_attrs->x_valid = TRUE;
629 	} else if (strcmp (attr->name, "y") == 0) {
630 	    dest_attrs->y = attr->scalar.f;
631 	    dest_attrs->y_valid = TRUE;
632 	} else if (strcmp (attr->name, "internal") == 0) {
633 	    dest_attrs->internal = attr->scalar.b;
634 	}
635     }
636 
637     if (! dest_attrs->name)
638 	status = _cairo_tag_error ("Destination attributes: \"%s\" missing name attribute",
639 				   attributes);
640 
641   cleanup:
642     free_attributes_list (&list);
643 
644     return status;
645 }
646 
647 cairo_int_status_t
_cairo_tag_parse_ccitt_params(const char * attributes,cairo_ccitt_params_t * ccitt_params)648 _cairo_tag_parse_ccitt_params (const char *attributes, cairo_ccitt_params_t *ccitt_params)
649 {
650     cairo_list_t list;
651     cairo_int_status_t status;
652     attribute_t *attr;
653 
654     ccitt_params->columns = -1;
655     ccitt_params->rows = -1;
656 
657     /* set defaults */
658     ccitt_params->k = 0;
659     ccitt_params->end_of_line = FALSE;
660     ccitt_params->encoded_byte_align = FALSE;
661     ccitt_params->end_of_block = TRUE;
662     ccitt_params->black_is_1 = FALSE;
663     ccitt_params->damaged_rows_before_error = 0;
664 
665     cairo_list_init (&list);
666     status = parse_attributes (attributes, _ccitt_params_spec, &list);
667     if (unlikely (status))
668 	goto cleanup;
669 
670     cairo_list_foreach_entry (attr, attribute_t, &list, link)
671     {
672 	if (strcmp (attr->name, "Columns") == 0) {
673 	    ccitt_params->columns = attr->scalar.i;
674 	} else if (strcmp (attr->name, "Rows") == 0) {
675 	    ccitt_params->rows = attr->scalar.i;
676 	} else if (strcmp (attr->name, "K") == 0) {
677 	    ccitt_params->k = attr->scalar.i;
678 	} else if (strcmp (attr->name, "EndOfLine") == 0) {
679 	    ccitt_params->end_of_line = attr->scalar.b;
680 	} else if (strcmp (attr->name, "EncodedByteAlign") == 0) {
681 	    ccitt_params->encoded_byte_align = attr->scalar.b;
682 	} else if (strcmp (attr->name, "EndOfBlock") == 0) {
683 	    ccitt_params->end_of_block = attr->scalar.b;
684 	} else if (strcmp (attr->name, "BlackIs1") == 0) {
685 	    ccitt_params->black_is_1 = attr->scalar.b;
686 	} else if (strcmp (attr->name, "DamagedRowsBeforeError") == 0) {
687 	    ccitt_params->damaged_rows_before_error = attr->scalar.b;
688 	}
689     }
690 
691   cleanup:
692     free_attributes_list (&list);
693 
694     return status;
695 }
696 
697 cairo_int_status_t
_cairo_tag_parse_eps_params(const char * attributes,cairo_eps_params_t * eps_params)698 _cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *eps_params)
699 {
700     cairo_list_t list;
701     cairo_int_status_t status;
702     attribute_t *attr;
703     attrib_val_t val;
704 
705     cairo_list_init (&list);
706     status = parse_attributes (attributes, _eps_params_spec, &list);
707     if (unlikely (status))
708 	goto cleanup;
709 
710     cairo_list_foreach_entry (attr, attribute_t, &list, link)
711     {
712 	if (strcmp (attr->name, "bbox") == 0) {
713 	    _cairo_array_copy_element (&attr->array, 0, &val);
714 	    eps_params->bbox.p1.x = val.f;
715 	    _cairo_array_copy_element (&attr->array, 1, &val);
716 	    eps_params->bbox.p1.y = val.f;
717 	    _cairo_array_copy_element (&attr->array, 2, &val);
718 	    eps_params->bbox.p2.x = val.f;
719 	    _cairo_array_copy_element (&attr->array, 3, &val);
720 	    eps_params->bbox.p2.y = val.f;
721 	}
722     }
723 
724   cleanup:
725     free_attributes_list (&list);
726 
727     return status;
728 }
729