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