Branch data Line data Source code
1 : : /* -*- mode: C -*-
2 : : *
3 : : * File: pdf-token-writer.c
4 : : * Date: Wed Sep 23 04:37:51 2009
5 : : *
6 : : * GNU PDF Library - Stream token writer
7 : : *
8 : : */
9 : :
10 : : /* Copyright (C) 2009-2011 Free Software Foundation, Inc. */
11 : :
12 : : /* This program is free software: you can redistribute it and/or modify
13 : : * it under the terms of the GNU General Public License as published by
14 : : * the Free Software Foundation, either version 3 of the License, or
15 : : * (at your option) any later version.
16 : : *
17 : : * This program is distributed in the hope that it will be useful,
18 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 : : * GNU General Public License for more details.
21 : : *
22 : : * You should have received a copy of the GNU General Public License
23 : : * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 : : */
25 : :
26 : : #include <config.h>
27 : :
28 : : #include <stdlib.h>
29 : : #include <string.h>
30 : :
31 : : #include <inttypes.h>
32 : : #include <unistr.h>
33 : :
34 : : #include <pdf-tokeniser.h>
35 : : #include <pdf-token-writer.h>
36 : :
37 : : /* Internal state */
38 : : struct pdf_token_writer_s {
39 : : pdf_stm_t *stream; /* stream to write bytes to */
40 : :
41 : : pdf_bool_t in_keyword;
42 : : pdf_size_t line_length;
43 : : pdf_size_t buffered_line_length;
44 : : pdf_size_t max_line_length;
45 : :
46 : : int state; /* This field is used by the writing routines to denote
47 : : several states in the writing of the tokens. Thus its
48 : : concrete meaning depends on the specific writing
49 : : routine. See the write_*_token functions in
50 : : pdf-token-writer.c for more information. */
51 : : pdf_size_t pos;
52 : : pdf_size_t paren_quoting_start, paren_quoting_end;
53 : : pdf_bool_t utf8;
54 : : pdf_buffer_t *buffer;
55 : : };
56 : :
57 : : /* Returns 255 on invalid hex values */
58 : : #define HEXCHAR(value) \
59 : : (value < 10 ? value + '0' : \
60 : : (value < 16 ? value - 10 + 'A' : \
61 : : 255))
62 : :
63 : : /* PDF32000 7.5.1: "lines that are not part of stream object data
64 : : * are limited to no more than 255 characters"... */
65 : : #define PDF_TOKW_MAX_LINE_LENGTH 255
66 : :
67 : : /* The buffer size is mostly arbitrary, but the buffer must be large
68 : : * enough for snprintf to write any possible floating point value.
69 : : * Any number over 50 should be fine. */
70 : : #define PDF_TOKW_BUFFER_SIZE 32768
71 : :
72 : : pdf_token_writer_t *
73 : 32 : pdf_token_writer_new (pdf_stm_t *stm,
74 : : pdf_error_t **error)
75 : : {
76 : 32 : pdf_token_writer_t *tokw = NULL;
77 : :
78 [ - + ]: 32 : PDF_ASSERT_POINTER_RETURN_VAL (stm, NULL);
79 : :
80 : : /* Allow only write streams */
81 [ - + ]: 32 : if (pdf_stm_get_mode (stm) != PDF_STM_WRITE)
82 : : {
83 : 0 : pdf_set_error (error,
84 : : PDF_EDOMAIN_BASE_TOKENISER,
85 : : PDF_EBADDATA,
86 : : "cannot create token writer: "
87 : : "write stream needed");
88 : 0 : return NULL;
89 : : }
90 : :
91 : 32 : tokw = pdf_alloc (sizeof (struct pdf_token_writer_s));
92 [ - + ]: 32 : if (!tokw)
93 : : {
94 : 0 : pdf_set_error (error,
95 : : PDF_EDOMAIN_BASE_TOKENISER,
96 : : PDF_ENOMEM,
97 : : "cannot create token writer: "
98 : : "couldn't allocate '%lu' bytes",
99 : : (unsigned long)sizeof (struct pdf_token_writer_s));
100 : 0 : return NULL;
101 : : }
102 : :
103 : 32 : tokw->buffer = pdf_buffer_new (PDF_TOKW_BUFFER_SIZE, error);
104 [ - + ]: 32 : if (!tokw->buffer)
105 : : {
106 : 0 : pdf_token_writer_destroy (tokw);
107 : 0 : return NULL;
108 : : }
109 : :
110 : : /* set max_line_length to 0 for no maximum */
111 : 32 : tokw->max_line_length = PDF_TOKW_MAX_LINE_LENGTH;
112 : 32 : tokw->stream = stm;
113 : :
114 [ - + ]: 32 : if (!pdf_token_writer_reset (tokw, error))
115 : : {
116 : 0 : pdf_token_writer_destroy (tokw);
117 : 0 : return NULL;
118 : : }
119 : :
120 : 32 : return tokw;
121 : : }
122 : :
123 : : pdf_bool_t
124 : 32 : pdf_token_writer_reset (pdf_token_writer_t *writer,
125 : : pdf_error_t **error)
126 : : {
127 [ - + ]: 32 : PDF_ASSERT_POINTER_RETURN_VAL (writer, PDF_FALSE);
128 : :
129 : 32 : writer->state = 0;
130 : 32 : writer->in_keyword = PDF_FALSE;
131 : 32 : writer->line_length = 0;
132 : 32 : return PDF_TRUE;
133 : : }
134 : :
135 : : void
136 : 32 : pdf_token_writer_destroy (pdf_token_writer_t *writer)
137 : : {
138 [ + - ]: 32 : if (!writer)
139 : : return;
140 : :
141 [ + - ]: 32 : if (writer->buffer)
142 : 32 : pdf_buffer_destroy (writer->buffer);
143 : 32 : pdf_dealloc (writer);
144 : : }
145 : :
146 : : /***** Unbuffered output *****/
147 : :
148 : : /* Write data to the stream. All output passes through this function. */
149 : : static pdf_bool_t
150 : 32 : write_data (pdf_token_writer_t *writer,
151 : : const pdf_uchar_t *data,
152 : : pdf_size_t len,
153 : : pdf_size_t *written,
154 : : pdf_error_t **error)
155 : : {
156 : : pdf_size_t i;
157 : 32 : pdf_error_t *inner_error = NULL;
158 : : pdf_size_t n_written;
159 : :
160 [ - + ][ # # ]: 32 : if (!pdf_stm_write (writer->stream, data, len, &n_written, &inner_error) &&
161 : 0 : inner_error)
162 : : {
163 [ # # ]: 0 : if (written)
164 : 0 : *written = n_written;
165 : 0 : pdf_propagate_error (error, inner_error);
166 : 0 : return PDF_FALSE;
167 : : }
168 : :
169 [ + + ]: 273 : for (i = 0; i < n_written; ++i)
170 : : {
171 : 241 : writer->line_length++;
172 [ + + ]: 241 : if (pdf_is_eol_char (data[i]))
173 : 2 : writer->line_length = 0;
174 : :
175 [ + + ][ + + ]: 241 : writer->in_keyword = pdf_is_regular_char (data[i]);
[ + + ][ + - ]
[ + - ][ + + ]
[ + + ][ + + ]
[ + + ][ + + ]
[ + - ][ + - ]
[ + - ][ + - ]
176 : : }
177 : :
178 [ + - ]: 32 : if (written)
179 : 32 : *written = n_written;
180 : 32 : return PDF_TRUE;
181 : : }
182 : :
183 : : static pdf_bool_t
184 : : write_char (pdf_token_writer_t *writer,
185 : : pdf_uchar_t ch,
186 : : pdf_error_t **error)
187 : : {
188 : 0 : return write_data (writer, &ch, 1, NULL, error);
189 : : }
190 : :
191 : : /* Write data starting at writer->pos, incrementing writer->pos as needed. */
192 : : static pdf_bool_t
193 : 0 : write_data_using_pos (pdf_token_writer_t *writer,
194 : : const pdf_uchar_t *data,
195 : : pdf_size_t len,
196 : : pdf_error_t **error)
197 : : {
198 : : pdf_size_t written;
199 : :
200 : : /* Just in case... */
201 [ # # ]: 0 : if (writer->pos > len)
202 : : {
203 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
204 : 0 : return PDF_FALSE;
205 : : }
206 : :
207 [ # # ]: 0 : while (writer->pos < len)
208 : : {
209 [ # # ]: 0 : if (!write_data (writer,
210 : : data + writer->pos,
211 : : len - writer->pos,
212 : : &written,
213 : : error))
214 : 0 : return PDF_FALSE;
215 : :
216 : 0 : writer->pos += written;
217 : : }
218 : :
219 : 0 : return PDF_TRUE;
220 : : }
221 : :
222 : : /***** Buffered output, buffer management *****/
223 : :
224 : : /* Write all buffered data to the stream. */
225 : : static pdf_bool_t
226 : 32 : flush_buffer (pdf_token_writer_t *writer,
227 : : pdf_error_t **error)
228 : : {
229 : : pdf_size_t len;
230 : :
231 [ + + ]: 64 : while ((len = writer->buffer->wp - writer->buffer->rp) > 0)
232 : : {
233 : : pdf_size_t written;
234 : :
235 [ - + ]: 32 : if (!write_data (writer,
236 : 64 : writer->buffer->data + writer->buffer->rp,
237 : : len,
238 : : &written,
239 : : error))
240 : 0 : return PDF_FALSE;
241 : :
242 : 32 : writer->buffer->rp += written;
243 : : }
244 : :
245 : 32 : pdf_buffer_rewind (writer->buffer);
246 : 32 : return PDF_TRUE;
247 : : }
248 : :
249 : : /* Flush the buffer if there are less than 'len' bytes free. */
250 : : static pdf_bool_t
251 : 178 : reserve_buffer_space (pdf_token_writer_t *writer,
252 : : pdf_size_t len,
253 : : pdf_error_t **error)
254 : : {
255 [ - + ]: 178 : if (writer->buffer->wp + len > writer->buffer->size)
256 : : {
257 [ # # ]: 0 : if (!flush_buffer (writer, error))
258 : 0 : return PDF_FALSE;
259 : :
260 [ # # ]: 0 : PDF_ASSERT_RETURN_VAL (len < writer->buffer->size, PDF_FALSE);
261 [ # # ]: 0 : PDF_ASSERT_RETURN_VAL (writer->buffer->wp == 0, PDF_FALSE);
262 : : }
263 : :
264 : 178 : return PDF_TRUE;
265 : : }
266 : :
267 : : /* Write a character into the buffer; this assumes it will fit. */
268 : : static void
269 : : write_buffered_char_nocheck (pdf_token_writer_t *writer,
270 : : pdf_char_t ch)
271 : : {
272 : 225 : writer->buffer->data[writer->buffer->wp++] = ch;
273 [ # # ][ # # ]: 225 : if (pdf_is_eol_char(ch))
[ + + ][ - +
- + - + ]
[ - + ]
[ - + - + ]
[ - + ][ - + ]
[ - + ]
274 : 2 : writer->buffered_line_length = 0;
275 : : else
276 : 223 : writer->buffered_line_length++;
277 : : }
278 : :
279 : : /* Write a character into the buffer. The buffer is flushed only if
280 : : * there's no room to write the character. */
281 : : static pdf_bool_t
282 : 116 : write_buffered_char (pdf_token_writer_t *writer,
283 : : pdf_char_t ch,
284 : : pdf_error_t **error)
285 : : {
286 [ - + ]: 116 : if (!reserve_buffer_space (writer, 1, error))
287 : 0 : return PDF_FALSE;
288 : :
289 : 116 : write_buffered_char_nocheck (writer, ch);
290 : 116 : return PDF_TRUE;
291 : : }
292 : :
293 : : /***** Misc. utility functions *****/
294 : :
295 : : /* Prepare to write a new token, adding some whitespace if necessary. */
296 : : static pdf_bool_t
297 : 32 : start_token (pdf_token_writer_t *writer,
298 : : pdf_bool_t need_wspace,
299 : : pdf_size_t len,
300 : : pdf_error_t **error)
301 : : {
302 : 32 : const pdf_uchar_t *add_char = NULL;
303 : : static const pdf_uchar_t wspace_char = ' ';
304 : : static const pdf_uchar_t newline_char = '\n';
305 : :
306 [ + + ][ - + ]: 32 : if (need_wspace && writer->in_keyword)
307 : : {
308 : 0 : add_char = &wspace_char;
309 : 0 : len++;
310 : : }
311 : :
312 : : /* If the token would make this line too long, start a new line. */
313 [ - + ][ # # ]: 32 : if (writer->line_length + len > writer->max_line_length &&
314 : 0 : writer->max_line_length > 0)
315 : : {
316 : 0 : add_char = &newline_char;
317 : : }
318 : :
319 [ - + ][ # # ]: 32 : return ((add_char &&
320 : : !write_data (writer, add_char, 1, NULL, error)) ?
321 : : PDF_FALSE : PDF_TRUE);
322 : : }
323 : :
324 : : /***** Numeric tokens *****/
325 : :
326 : : /* Encode snprintf output for PDF. 'len' is the return value of snprintf.
327 : : * Re-encodes bytes 0 to 'len' of writer->buffer and resets buffer->rp/wp. */
328 : : static pdf_bool_t
329 : 5 : encode_buffer_number (pdf_token_writer_t *writer,
330 : : pdf_error_t **error,
331 : : const pdf_char_t *number_format,
332 : : ...)
333 : : {
334 : : int len;
335 : : va_list args;
336 : :
337 : 5 : va_start (args, number_format);
338 : :
339 : : /* Print the number in the buffer */
340 : 10 : len = vsnprintf ((char *)writer->buffer->data,
341 : 5 : writer->buffer->size,
342 : : number_format,
343 : : args);
344 : 5 : va_end (args);
345 : :
346 [ - + ]: 5 : if (len < 0)
347 : : {
348 : 0 : pdf_set_error (error,
349 : : PDF_EDOMAIN_BASE_TOKENISER,
350 : : PDF_ERROR,
351 : : "couldn't encode number in buffer");
352 : 0 : return PDF_FALSE;
353 : : }
354 [ - + ]: 5 : else if (len >= writer->buffer->size)
355 : : {
356 : : /* TODO: Can't we just reserve more buffer space? */
357 : 0 : pdf_set_error (error,
358 : : PDF_EDOMAIN_BASE_TOKENISER,
359 : : PDF_EAGAIN,
360 : : "couldn't encode number in buffer: "
361 : : "not enough space (%u needed, %u available)",
362 : : (unsigned long)len,
363 : 0 : (unsigned long)writer->buffer->size);
364 : 0 : return PDF_FALSE;
365 : : }
366 : :
367 : 5 : writer->buffer->wp = 0;
368 : 5 : writer->buffer->rp = 0;
369 [ + + ]: 38 : while (writer->buffer->rp < len)
370 : : {
371 : 33 : pdf_char_t ch = writer->buffer->data[writer->buffer->rp];
372 : :
373 [ + + ]: 33 : if ((ch == '-') ||
374 : 33 : (ch >= '0' && ch <= '9'))
375 : : {
376 : 30 : writer->buffer->rp++;
377 : 30 : writer->buffer->data[writer->buffer->wp++] = ch;
378 : : }
379 : : else
380 : : {
381 : : /* This should be a decimal point; check it. */
382 : : const pdf_char_t *decimal_point;
383 : : pdf_size_t declen;
384 : :
385 : 3 : decimal_point = pdf_tokeniser_get_decimal_point ();
386 : 3 : declen = strlen (decimal_point);
387 : :
388 [ - + ]: 3 : if (memcmp (writer->buffer->data + writer->buffer->rp,
389 : : decimal_point,
390 : : declen) != 0)
391 : : {
392 : 0 : pdf_set_error (error,
393 : : PDF_EDOMAIN_BASE_TOKENISER,
394 : : PDF_ERROR,
395 : : "couldn't encode number in buffer: "
396 : : "expecting decimal point, unexpected char found");
397 : 0 : return PDF_FALSE;
398 : : }
399 : :
400 : 3 : writer->buffer->rp += declen;
401 : 3 : writer->buffer->data[writer->buffer->wp++] = '.';
402 : : }
403 : : }
404 : :
405 : 5 : writer->buffer->rp = 0;
406 : 5 : return PDF_TRUE; /* success */
407 : : }
408 : :
409 : : static pdf_bool_t
410 : 2 : write_integer_token (pdf_token_writer_t *writer,
411 : : const pdf_token_t *token,
412 : : pdf_error_t **error)
413 : : {
414 [ + - - - ]: 2 : switch (writer->state)
415 : : {
416 : : case 0:
417 [ - + ]: 2 : if (!encode_buffer_number (writer,
418 : : error,
419 : : "%"PRId32,
420 : : pdf_token_get_integer_value (token)))
421 : 0 : return PDF_FALSE;
422 : 2 : writer->state++;
423 : : /* fall through */
424 : :
425 : : case 1:
426 [ - + ]: 2 : if (!start_token (writer,
427 : : PDF_TRUE /* need_wspace */,
428 : 2 : writer->buffer->wp,
429 : : error))
430 : 0 : return PDF_FALSE;
431 : 2 : writer->state++;
432 : : /* fall through */
433 : :
434 : : case 2:
435 : 2 : return flush_buffer (writer, error);
436 : : default:
437 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
438 : 2 : return PDF_FALSE;
439 : : }
440 : : }
441 : :
442 : : static pdf_bool_t
443 : 3 : write_real_token (pdf_token_writer_t *writer,
444 : : const pdf_token_t *token,
445 : : pdf_error_t **error)
446 : : {
447 [ + - - - ]: 3 : switch (writer->state)
448 : : {
449 : : case 0:
450 : : {
451 : : pdf_real_t value;
452 : :
453 : 3 : value = pdf_token_get_real_value (token);
454 [ + - - + ]: 3 : if (isnan (value) || isinf (value))
455 : : {
456 [ # # ]: 0 : pdf_set_error (error,
457 : : PDF_EDOMAIN_BASE_TOKENISER,
458 : : PDF_EBADDATA,
459 : : "cannot write real token: %s value",
460 : 0 : isnan (value) ? "NaN" : "Infinite");
461 : 0 : return PDF_FALSE;
462 : : }
463 : :
464 [ - + ]: 3 : if (!encode_buffer_number (writer,
465 : : error,
466 : : "%#f",
467 : : value))
468 : 0 : return PDF_FALSE;
469 : :
470 : : /* strip trailing zeroes */
471 [ + - ][ + + ]: 20 : while (writer->buffer->wp &&
472 : 20 : writer->buffer->data[writer->buffer->wp - 1] == '0')
473 : 17 : writer->buffer->wp--;
474 : : }
475 : 3 : writer->state++;
476 : : /* fall through */
477 : :
478 : : case 1:
479 [ - + ]: 3 : if (!start_token (writer,
480 : : PDF_TRUE /* need_wspace */,
481 : 3 : writer->buffer->wp,
482 : : error))
483 : 0 : return PDF_FALSE;;
484 : 3 : writer->state++;
485 : : /* fall through */
486 : :
487 : : case 2:
488 : 3 : return flush_buffer (writer, error);
489 : :
490 : : default:
491 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
492 : 3 : return PDF_FALSE;
493 : : }
494 : : }
495 : :
496 : : /***** String tokens *****/
497 : :
498 : : static pdf_bool_t
499 : : should_escape_strchar (pdf_u32_t flags,
500 : : pdf_char_t ch,
501 : : pdf_bool_t quote_parens,
502 : : pdf_bool_t is_utf8)
503 : : {
504 [ + + ][ + + ]: 114 : if (ch == '\\' || ch == '\r')
505 : 8 : return PDF_TRUE;
506 : :
507 [ + + ][ + + ]: 106 : if (ch == '(' || ch == ')')
508 : 8 : return quote_parens;
509 : :
510 [ + + ][ + + ]: 98 : if (flags & PDF_TOKEN_READABLE_STRINGS)
511 : : {
512 [ + - ][ + + ]: 26 : if (ch == 127 ||
[ + - ][ + + ]
513 : 26 : (ch < 32 && ch != 10) ||
514 : : (ch >= 128 && !is_utf8))
515 : 10 : return PDF_TRUE;
516 : : }
517 : :
518 : 88 : return PDF_FALSE;
519 : : }
520 : :
521 : : static pdf_i32_t
522 : : str_escape_len (const pdf_char_t *data,
523 : : pdf_size_t len,
524 : : pdf_size_t pos)
525 : : {
526 [ + + ]: 13 : switch (data[pos])
527 : : {
528 : : /* characters with two-character escape codes */
529 : : case 8:
530 : : case 9:
531 : : case 10:
532 : : case 12:
533 : : case 13:
534 : : case 40:
535 : : case 41:
536 : : case 92:
537 : 12 : return 2;
538 : : }
539 : :
540 [ - + ]: 1 : if (data[pos] >= 0100)
541 : 0 : return 4; /* escaped using a backslash and 3 octal characters */
542 : :
543 [ + - ]: 1 : if (pos + 1 < len)
544 : : {
545 [ - + ]: 1 : if (data[pos + 1] >= '0' && data[pos + 1] <= '9')
546 : 0 : return 4; /* need to write a 3-character octal number */
547 : : }
548 : :
549 [ - + ]: 1 : return (data[pos] >= 010) ? 3 : 2;
550 : : }
551 : :
552 : : static void
553 : 14 : scan_string (pdf_token_writer_t *writer,
554 : : pdf_u32_t flags,
555 : : const pdf_char_t *data,
556 : : pdf_size_t len,
557 : : pdf_bool_t *use_hex)
558 : : {
559 : : pdf_size_t i;
560 : 14 : pdf_size_t enc_bytes = 0;
561 : :
562 : : /* Match parentheses, and determine the portion of the string
563 : : * in which they should be quoted. */
564 : 14 : writer->paren_quoting_start = 0;
565 : 14 : writer->paren_quoting_end = len;
566 : :
567 [ + + ]: 71 : for (i = 0; i < len; ++i)
568 : : {
569 [ + + ]: 57 : if (data[i] == '(')
570 : : {
571 : : pdf_size_t j;
572 : :
573 [ + + ]: 6 : for (j = writer->paren_quoting_end - 1; j > i; --j)
574 : : {
575 : : /* find a matching ')' */
576 [ - + ]: 4 : if (data[j] == ')')
577 : : {
578 : 0 : writer->paren_quoting_end = j;
579 : 0 : writer->paren_quoting_start = i + 1;
580 : 0 : break;
581 : : }
582 : : }
583 : : }
584 : : }
585 : :
586 : : /* Determine the size of the escaped string. */
587 [ + + + - ]: 17 : writer->utf8 = ((flags & PDF_TOKEN_READABLE_STRINGS) &&
588 : 3 : (u8_check ((uint8_t *) data, len) == NULL));
589 : :
590 [ + + ]: 71 : for (i = 0; i < len; ++i)
591 : : {
592 [ + + ]: 57 : if (should_escape_strchar (flags,
593 : 57 : data[i],
594 [ + - ][ + - ]: 57 : ((i >= writer->paren_quoting_start &&
595 : 57 : i < writer->paren_quoting_end) ?
596 : : PDF_TRUE : PDF_FALSE),
597 : 57 : writer->utf8))
598 : 13 : enc_bytes += str_escape_len (data, len, i);
599 : : else
600 : 44 : enc_bytes++;
601 : : }
602 : 14 : *use_hex = (enc_bytes > len * 2);
603 : 14 : }
604 : :
605 : : static pdf_bool_t
606 : 57 : write_string_char (pdf_token_writer_t *writer,
607 : : pdf_u32_t flags,
608 : : const pdf_char_t *data,
609 : : pdf_char_t len,
610 : : pdf_size_t pos,
611 : : pdf_error_t **error)
612 : : {
613 : : const pdf_char_t *output;
614 : 57 : pdf_size_t outlen = 1;
615 : 57 : pdf_char_t esc[4] = { '\\', 0, 0, 0 };
616 : : pdf_char_t ch;
617 : : pdf_bool_t quote_parens;
618 : : pdf_size_t i;
619 : :
620 : 57 : output = data + pos;
621 : 57 : ch = data[pos];
622 [ + - ][ + - ]: 57 : quote_parens = (pos >= writer->paren_quoting_start &&
623 : 57 : pos < writer->paren_quoting_end);
624 : :
625 [ + + ]: 57 : if (should_escape_strchar (flags, ch, quote_parens, writer->utf8))
626 : : {
627 : : /* escape the character */
628 : 13 : output = esc;
629 : 13 : outlen = 2;
630 : :
631 [ - + - + + : 13 : switch (ch)
+ + ]
632 : : {
633 : 0 : case 8: esc[1] = 'b'; break;
634 : 2 : case 9: esc[1] = 't'; break;
635 : 0 : case 10: esc[1] = 'n'; break;
636 : 2 : case 12: esc[1] = 'f'; break;
637 : 2 : case 13: esc[1] = 'r'; break;
638 : : case 40: /* '('; fall through */
639 : : case 41: /* ')'; fall through */
640 : : case 92: /* '\\' */
641 : 6 : esc[1] = ch;
642 : 6 : break;
643 : : default: /* use an octal escape */
644 : : {
645 : : pdf_size_t digits;
646 : : pdf_char_t nextch;
647 : :
648 [ + - ]: 1 : nextch = (pos + 1 < len) ? data[pos + 1] : 0;
649 [ - + ]: 1 : if (nextch >= '0' && nextch <= '9')
650 : 0 : digits = 3; /* must use 3 octal characters */
651 [ - + ]: 1 : else if (ch > 0100)
652 : 0 : digits = 3;
653 [ - + ]: 1 : else if (ch > 010)
654 : 0 : digits = 2;
655 : : else
656 : 1 : digits = 1;
657 : :
658 : 1 : outlen = 1;
659 [ - - + - ]: 1 : switch (digits)
660 : : {
661 : : /* fall through each case */
662 : 0 : case 3: esc[outlen++] = HEXCHAR (ch / 0100);
663 [ # # ]: 0 : case 2: esc[outlen++] = HEXCHAR ((ch % 0100) / 010);
664 [ + - ][ # # ]: 1 : case 1: esc[outlen++] = HEXCHAR (ch % 010);
665 : : }
666 : : }
667 : : }
668 : : }
669 : :
670 : : /* If the line will be too long, split it (the length cannot be equal to
671 : : * the maximum, since this would leave no room for the backslash). */
672 [ + - ][ + + ]: 57 : if (writer->max_line_length > 0 &&
[ - + ]
673 : 57 : !pdf_is_eol_char (output[0]) &&
674 : 55 : writer->buffered_line_length + outlen >= writer->max_line_length)
675 : : {
676 [ # # ]: 0 : if (!reserve_buffer_space (writer, 2, error))
677 : 0 : return PDF_FALSE;
678 : :
679 : : write_buffered_char_nocheck (writer, '\\');
680 : : write_buffered_char_nocheck (writer, '\n'); /* newline */
681 : : }
682 : :
683 [ - + ]: 57 : if (!reserve_buffer_space (writer, outlen, error))
684 : 0 : return PDF_FALSE;
685 : :
686 [ + + ]: 127 : for (i = 0; i < outlen; ++i)
687 : 70 : write_buffered_char_nocheck (writer, output[i]);
688 : :
689 : 57 : return PDF_TRUE;
690 : : }
691 : :
692 : : static pdf_bool_t
693 : 16 : write_string_token (pdf_token_writer_t *writer,
694 : : pdf_u32_t flags,
695 : : const pdf_token_t *token,
696 : : pdf_error_t **error)
697 : : {
698 : : const pdf_char_t *data;
699 : : pdf_size_t size;
700 : :
701 : 16 : data = pdf_token_get_string_data (token);
702 : 16 : size = pdf_token_get_string_size (token);
703 : :
704 [ + - - - - : 16 : switch (writer->state)
- - - - - ]
705 : : {
706 : : case 0:
707 : : {
708 : 16 : pdf_bool_t use_hex = (flags & PDF_TOKEN_HEX_STRINGS);
709 : :
710 [ + + ]: 16 : if (!use_hex)
711 : 14 : scan_string (writer, flags, data, size, &use_hex);
712 : :
713 [ + + ]: 16 : if (use_hex)
714 : : goto hexstring_start;
715 : : }
716 : 14 : writer->state++;
717 : : /* fall through */
718 : :
719 : : case 1:
720 : : {
721 : : /* Passing a correct length to start_token isn't important
722 : : * since we can split the string across multiple lines. */
723 : 14 : pdf_size_t dummy_len = PDF_MIN (20, 2 + size);
724 : :
725 [ - + ]: 14 : if (!start_token (writer,
726 : : PDF_FALSE /* need_wspace */,
727 : : dummy_len,
728 : : error))
729 : 0 : return PDF_FALSE;
730 : : }
731 : :
732 : 14 : pdf_buffer_rewind (writer->buffer);
733 : 14 : writer->buffered_line_length = writer->line_length;
734 : : write_buffered_char_nocheck (writer, '(');
735 : 14 : writer->pos = 0;
736 : 14 : writer->state++;
737 : : /* fall through */
738 : :
739 : : case 2:
740 [ + + ]: 71 : while (writer->pos < size)
741 : : {
742 [ - + ]: 57 : if (!write_string_char (writer,
743 : : flags,
744 : : data,
745 : : size,
746 : : writer->pos,
747 : : error))
748 : 0 : return PDF_FALSE;
749 : 57 : writer->pos++;
750 : : }
751 : 14 : writer->state++;
752 : : /* fall through */
753 : :
754 : : case 3:
755 [ - + ]: 14 : if (!write_buffered_char (writer, ')', error))
756 : 0 : return PDF_FALSE;
757 : 14 : writer->state++;
758 : : /* fall through */
759 : :
760 : : case 4:
761 : 14 : return flush_buffer (writer, error);
762 : :
763 : : /*** hex strings ***/
764 : : hexstring_start:
765 : 2 : writer->state = 101;
766 : : case 101:
767 : : {
768 : 2 : pdf_size_t dummy_len = PDF_MIN (20, 2 + size * 2);
769 : :
770 [ - + ]: 2 : if (!start_token (writer,
771 : : PDF_FALSE /* need_wspace */,
772 : : dummy_len,
773 : : error))
774 : 0 : return PDF_FALSE;
775 : : }
776 : :
777 : 2 : pdf_buffer_rewind (writer->buffer);
778 : 2 : writer->buffered_line_length = writer->line_length;
779 : : write_buffered_char_nocheck (writer, '<');
780 : 2 : writer->pos = 0;
781 : 2 : writer->state++;
782 : : /* fall through */
783 : :
784 : : case 102:
785 [ + + ]: 5 : while (writer->pos < size)
786 : : {
787 : : pdf_char_t ch;
788 : :
789 : : /* If this line would be too long, start a new one. */
790 [ - + ][ # # ]: 3 : if (writer->buffered_line_length + 2 > writer->max_line_length
791 : 3 : && writer->max_line_length > 0)
792 : : {
793 [ # # ]: 0 : if (!write_buffered_char (writer, '\n', error))
794 : 0 : return PDF_FALSE;
795 : : }
796 : :
797 : 3 : ch = data[writer->pos];
798 [ - + ]: 3 : if (!reserve_buffer_space (writer, 2, error))
799 : 0 : return PDF_FALSE;
800 : :
801 : 3 : write_buffered_char_nocheck (writer, HEXCHAR (ch / 16));
802 [ + + ][ + - ]: 3 : if (writer->pos != (size - 1) ||
803 : 1 : (ch % 16) != 0)
804 [ + - ][ # # ]: 3 : write_buffered_char_nocheck (writer, HEXCHAR (ch % 16));
805 : 3 : writer->pos++;
806 : : }
807 : 2 : writer->state++;
808 : : /* fall through */
809 : :
810 : : case 103:
811 [ - + ]: 2 : if (!write_buffered_char (writer, '>', error))
812 : 0 : return PDF_FALSE;
813 : :
814 : 2 : writer->state++;
815 : : /* fall through */
816 : :
817 : : case 104:
818 : 2 : return flush_buffer (writer, error);
819 : :
820 : : default:
821 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
822 : 16 : return PDF_FALSE;
823 : : }
824 : : }
825 : :
826 : : /***** Other tokens *****/
827 : :
828 : : static pdf_bool_t
829 : 204 : should_escape_namechar (pdf_u32_t flags,
830 : : pdf_char_t ch,
831 : : pdf_bool_t *escape)
832 : : {
833 [ - + ]: 204 : if (!ch)
834 : 0 : return PDF_FALSE;
835 : :
836 [ + - ][ + + ]: 204 : *escape = !pdf_is_regular_char (ch);
[ + - ][ + - ]
[ + - ][ + - ]
[ + - ][ - + ]
837 [ - + ]: 204 : if (flags & PDF_TOKEN_NO_NAME_ESCAPES)
838 : : {
839 [ # # ]: 0 : if (*escape)
840 : 0 : return PDF_FALSE;
841 : : }
842 : : else
843 : : {
844 [ + + ][ + + ]: 204 : *escape = (*escape ||
[ - + ]
845 : 202 : ch == '#' ||
846 : 202 : ch < 33 ||
847 : : ch >= 127);
848 : : }
849 : :
850 : 204 : return PDF_TRUE;
851 : : }
852 : :
853 : : static pdf_bool_t
854 : 11 : write_name_token (pdf_token_writer_t *writer,
855 : : pdf_u32_t flags,
856 : : const pdf_token_t *token,
857 : : pdf_error_t **error)
858 : : {
859 : : pdf_size_t size;
860 : : const pdf_char_t *data;
861 : :
862 : 11 : data = pdf_token_get_name_data (token);
863 : 11 : size = pdf_token_get_name_size (token);
864 : :
865 [ + - - - : 11 : switch (writer->state)
- ]
866 : : {
867 : : case 0:
868 : : {
869 : : pdf_size_t i;
870 : :
871 : : /* Validate the name; also calculate the encoded size
872 : : * and store it in ->pos temporarily. */
873 : :
874 : 11 : writer->pos = 1 + size;
875 [ + + ]: 113 : for (i = 0; i < size; ++i)
876 : : {
877 : : pdf_bool_t escape;
878 : :
879 [ - + ]: 102 : if (!should_escape_namechar (flags, data[i], &escape))
880 : : {
881 : 0 : pdf_set_error (error,
882 : : PDF_EDOMAIN_BASE_TOKENISER,
883 : : PDF_EBADDATA,
884 : : "cannot write name token: "
885 : : "bad name");
886 : 0 : return PDF_FALSE;
887 : : }
888 : :
889 [ + + ]: 102 : if (escape)
890 : 2 : writer->pos += 2; /* 2 hex characters */
891 : : }
892 : :
893 : 11 : pdf_buffer_rewind (writer->buffer);
894 : : write_buffered_char_nocheck (writer, '/');
895 : : }
896 : 11 : writer->state++;
897 : : /* fall through */
898 : :
899 : : case 1:
900 [ - + ]: 11 : if (!start_token (writer,
901 : : PDF_FALSE, /* need_wspace */
902 : : writer->pos, /* encoded token length */
903 : : error))
904 : 0 : return PDF_FALSE;
905 : :
906 : 11 : writer->pos = 0;
907 : 11 : writer->state++;
908 : : /* fall through */
909 : :
910 : : case 2:
911 [ + + ]: 113 : while (writer->pos < size)
912 : : {
913 : : pdf_bool_t escape;
914 : : pdf_char_t ch;
915 : :
916 : 102 : ch = data[writer->pos];
917 [ - + ]: 102 : if (!should_escape_namechar (flags, ch, &escape))
918 : : {
919 : 0 : pdf_set_error (error,
920 : : PDF_EDOMAIN_BASE_TOKENISER,
921 : : PDF_EBADDATA,
922 : : "cannot write name token: "
923 : : "bad name");
924 : 0 : return PDF_FALSE;
925 : : }
926 : :
927 [ + + ]: 102 : if (escape)
928 : : {
929 [ - + ]: 2 : if (!reserve_buffer_space (writer, 3, error))
930 : 0 : return PDF_FALSE;
931 : :
932 : : write_buffered_char_nocheck (writer, '#');
933 : 2 : write_buffered_char_nocheck (writer, HEXCHAR (ch / 16));
934 [ + + ][ + - ]: 2 : write_buffered_char_nocheck (writer, HEXCHAR (ch % 16));
935 : : }
936 : : else
937 : : {
938 [ - + ]: 100 : if (!write_buffered_char (writer, ch, error))
939 : 0 : return PDF_FALSE;
940 : : }
941 : 102 : writer->pos++;
942 : : }
943 : 11 : writer->state++;
944 : : /* fall through */
945 : :
946 : : case 3:
947 : 11 : return flush_buffer (writer, error);
948 : :
949 : : default:
950 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
951 : 11 : return PDF_FALSE;
952 : : }
953 : : }
954 : :
955 : : static pdf_bool_t
956 : 0 : write_keyword_token (pdf_token_writer_t *writer,
957 : : const pdf_token_t *token,
958 : : pdf_error_t **error)
959 : : {
960 : : const pdf_char_t *data;
961 : : pdf_size_t size;
962 : :
963 : 0 : data = pdf_token_get_keyword_data (token);
964 : 0 : size = pdf_token_get_keyword_size (token);
965 : :
966 [ # # # # ]: 0 : switch (writer->state)
967 : : {
968 : : case 0:
969 [ # # ]: 0 : if (memchr (data, 0, size))
970 : : {
971 : 0 : pdf_set_error (error,
972 : : PDF_EDOMAIN_BASE_TOKENISER,
973 : : PDF_EBADDATA,
974 : : "cannot write keyword token: "
975 : : "contains NUL bytes");
976 : 0 : return PDF_FALSE;
977 : : }
978 : 0 : writer->state++;
979 : : /* fall through */
980 : :
981 : : case 1:
982 [ # # ]: 0 : if (!start_token (writer,
983 : : PDF_TRUE /* need_wspace */,
984 : : size,
985 : : error))
986 : 0 : return PDF_FALSE;
987 : :
988 : 0 : writer->pos = 0;
989 : 0 : writer->state++;
990 : : /* fall through */
991 : :
992 : : case 2:
993 : 0 : return write_data_using_pos (writer,
994 : : (pdf_uchar_t *)pdf_token_get_keyword_data (token),
995 : : size,
996 : : error);
997 : :
998 : : default:
999 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
1000 : 0 : return PDF_FALSE;
1001 : : }
1002 : : }
1003 : :
1004 : : static pdf_bool_t
1005 : 0 : write_comment_token (pdf_token_writer_t *writer,
1006 : : const pdf_token_t *token,
1007 : : pdf_error_t **error)
1008 : : {
1009 : : const pdf_char_t *data;
1010 : : pdf_size_t size;
1011 : :
1012 : 0 : data = pdf_token_get_comment_data (token);
1013 : 0 : size = pdf_token_get_comment_size (token);
1014 : :
1015 [ # # # # # : 0 : switch (writer->state)
# ]
1016 : : {
1017 : : case 0:
1018 : : {
1019 : : /* A comment can't span multiple lines. */
1020 : : pdf_size_t i;
1021 : :
1022 [ # # ]: 0 : for (i = 0; i < size; ++i)
1023 : : {
1024 [ # # ]: 0 : if (pdf_is_eol_char (data[i]))
1025 : : {
1026 : 0 : pdf_set_error (error,
1027 : : PDF_EDOMAIN_BASE_TOKENISER,
1028 : : PDF_EBADDATA,
1029 : : "cannot write comment token: "
1030 : : "comment must not span multiple lines");
1031 : 0 : return PDF_FALSE;
1032 : : }
1033 : : }
1034 : : }
1035 : 0 : writer->state++;
1036 : : /* fall through */
1037 : :
1038 : : case 1:
1039 [ # # ]: 0 : if (!start_token (writer,
1040 : : PDF_FALSE, /* need_wspace */
1041 : : size + 1,
1042 : : error))
1043 : 0 : return PDF_FALSE;
1044 : 0 : writer->state++;
1045 : : /* fall through */
1046 : :
1047 : : case 2:
1048 [ # # ]: 0 : if (!write_char (writer, '%', error))
1049 : 0 : return PDF_FALSE;
1050 : 0 : writer->pos = 0;
1051 : 0 : writer->state++;
1052 : : /* fall through */
1053 : :
1054 : : case 3:
1055 [ # # ]: 0 : if (!write_data_using_pos (writer, (pdf_uchar_t *)data, size, error))
1056 : 0 : return PDF_FALSE;
1057 : 0 : writer->state++;
1058 : : /* fall through */
1059 : :
1060 : : case 4:
1061 : 0 : return write_char (writer, '\n', error);
1062 : :
1063 : : default:
1064 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
1065 : 0 : return PDF_FALSE;
1066 : : }
1067 : : }
1068 : :
1069 : : static pdf_bool_t
1070 : 0 : write_valueless_token (pdf_token_writer_t *writer,
1071 : : pdf_char_t ch,
1072 : : pdf_size_t len,
1073 : : pdf_error_t **error)
1074 : : {
1075 : 0 : pdf_uchar_t buf[2] = {ch , ch};
1076 : :
1077 [ # # # ]: 0 : switch (writer->state)
1078 : : {
1079 : : case 0:
1080 [ # # ]: 0 : if (!start_token (writer,
1081 : : PDF_FALSE, /* need_wspace */
1082 : : len,
1083 : : error))
1084 : 0 : return PDF_FALSE;
1085 : 0 : writer->pos = 0;
1086 : 0 : writer->state++;
1087 : : /* fall through */
1088 : :
1089 : : case 1:
1090 : 0 : return write_data_using_pos (writer, buf, len, error);
1091 : :
1092 : : default:
1093 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
1094 : 0 : return PDF_FALSE;
1095 : : }
1096 : : }
1097 : :
1098 : : /***** Token dispatching *****/
1099 : :
1100 : : pdf_bool_t
1101 : 32 : pdf_token_writer_write (pdf_token_writer_t *writer,
1102 : : pdf_u32_t flags,
1103 : : const pdf_token_t *token,
1104 : : pdf_error_t **error)
1105 : : {
1106 : : pdf_bool_t rv;
1107 : :
1108 [ + + + + - : 32 : switch (pdf_token_get_type (token))
- - - - -
- - - ]
1109 : : {
1110 : : case PDF_TOKEN_INTEGER:
1111 : 2 : rv = write_integer_token (writer, token, error);
1112 : 2 : break;
1113 : : case PDF_TOKEN_REAL:
1114 : 3 : rv = write_real_token (writer, token, error);
1115 : 3 : break;
1116 : : case PDF_TOKEN_STRING:
1117 : 16 : rv = write_string_token (writer, flags, token, error);
1118 : 16 : break;
1119 : : case PDF_TOKEN_NAME:
1120 : 11 : rv = write_name_token (writer, flags, token, error);
1121 : 11 : break;
1122 : : case PDF_TOKEN_KEYWORD:
1123 : 0 : rv = write_keyword_token (writer, token, error);
1124 : 0 : break;
1125 : : case PDF_TOKEN_COMMENT:
1126 : 0 : rv = write_comment_token (writer, token, error);
1127 : 0 : break;
1128 : : case PDF_TOKEN_DICT_START:
1129 : 0 : rv = write_valueless_token (writer, '<', 2, error);
1130 : 0 : break;
1131 : : case PDF_TOKEN_DICT_END:
1132 : 0 : rv = write_valueless_token (writer, '>', 2, error);
1133 : 0 : break;
1134 : : case PDF_TOKEN_ARRAY_START:
1135 : 0 : rv = write_valueless_token (writer, '[', 1, error);
1136 : 0 : break;
1137 : : case PDF_TOKEN_ARRAY_END:
1138 : 0 : rv = write_valueless_token (writer, ']', 1, error);
1139 : 0 : break;
1140 : : case PDF_TOKEN_PROC_START:
1141 : 0 : rv = write_valueless_token (writer, '{', 1, error);
1142 : 0 : break;
1143 : : case PDF_TOKEN_PROC_END:
1144 : 0 : rv = write_valueless_token (writer, '}', 1, error);
1145 : 0 : break;
1146 : : default:
1147 : 0 : PDF_ASSERT_TRACE_NOT_REACHED ();
1148 : 0 : return PDF_FALSE;
1149 : : }
1150 : :
1151 [ + - ]: 32 : if (rv)
1152 : 32 : writer->state = 0;
1153 : 32 : return rv;
1154 : : }
1155 : :
1156 : : /* End of pdf-token-writer.c */
|