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 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 <assert.h>
29 : #include <stdlib.h>
30 : #include <string.h>
31 : #include <locale.h>
32 : #include <inttypes.h>
33 : #include <math.h>
34 :
35 : #include <pdf-token-writer.h>
36 : #include <unistr.h>
37 :
38 : pdf_status_t
39 : pdf_token_writer_new (pdf_stm_t stm, pdf_token_writer_t *writer)
40 0 : {
41 : pdf_status_t err;
42 : pdf_token_writer_t new_tokw;
43 :
44 0 : err = PDF_ENOMEM;
45 0 : new_tokw = pdf_alloc (sizeof (*new_tokw));
46 0 : if (!new_tokw)
47 0 : goto fail;
48 :
49 : /* determine the current locale's decimal point
50 : * (avoid using localeconv since it may not be thread-safe) */
51 0 : new_tokw->decimal_point = NULL;
52 : {
53 : int len;
54 : char decpt[16];
55 :
56 0 : err = PDF_ERROR;
57 0 : len = snprintf (decpt, sizeof (decpt), "%#.0f", 1.0);
58 0 : if (len <= 0 || (pdf_size_t)len >= sizeof (decpt)) /* shouldn't happen */
59 0 : goto fail;
60 :
61 0 : err = PDF_ENOMEM;
62 0 : new_tokw->decimal_point = pdf_alloc (len);
63 0 : if (!new_tokw->decimal_point)
64 0 : goto fail;
65 :
66 : /* this copies the trailing '\0' due to the starting offset */
67 0 : memcpy (new_tokw->decimal_point, &decpt[1], len);
68 : }
69 :
70 : /* set max_line_length to 0 for no maximum */
71 0 : new_tokw->max_line_length = PDF_TOKW_MAX_LINE_LENGTH;
72 :
73 0 : err = PDF_ENOMEM;
74 0 : new_tokw->buffer = pdf_buffer_new (PDF_TOKW_BUFFER_SIZE);
75 0 : if (!new_tokw->buffer)
76 0 : goto fail;
77 :
78 0 : err = PDF_EBADDATA;
79 0 : if (!stm || pdf_stm_get_mode (stm) != PDF_STM_WRITE)
80 : goto fail;
81 0 : new_tokw->stream = stm;
82 :
83 0 : pdf_token_writer_reset (new_tokw);
84 :
85 0 : *writer = new_tokw;
86 0 : return PDF_OK;
87 :
88 0 : fail:
89 0 : if (new_tokw)
90 0 : pdf_dealloc (new_tokw->decimal_point);
91 0 : pdf_dealloc (new_tokw);
92 :
93 0 : return err;
94 : }
95 :
96 : pdf_status_t
97 : pdf_token_writer_reset (pdf_token_writer_t writer)
98 0 : {
99 0 : writer->stage = 0;
100 0 : writer->in_keyword = PDF_FALSE;
101 0 : writer->line_length = 0;
102 0 : return PDF_OK;
103 : }
104 :
105 : pdf_status_t
106 : pdf_token_writer_destroy (pdf_token_writer_t writer)
107 0 : {
108 0 : if (!writer) return PDF_EBADDATA;
109 :
110 0 : assert (writer->buffer);
111 0 : if (writer->buffer)
112 0 : pdf_buffer_destroy (writer->buffer);
113 0 : pdf_dealloc (writer->decimal_point);
114 0 : pdf_dealloc (writer);
115 :
116 0 : return PDF_OK;
117 : }
118 :
119 :
120 : /***** Unbuffered output *****/
121 :
122 : /* Write data to the stream. All output passes through this function. */
123 : static pdf_status_t
124 : write_data (pdf_token_writer_t writer, const pdf_char_t *data,
125 : pdf_size_t len, pdf_size_t *written)
126 0 : {
127 : pdf_size_t i;
128 : pdf_status_t rv;
129 :
130 0 : rv = pdf_stm_write (writer->stream, data, len, written);
131 0 : if (rv != PDF_OK)
132 0 : return rv;
133 :
134 0 : for (i = 0; i < *written; ++i)
135 : {
136 0 : pdf_char_t ch = data[i];
137 0 : ++writer->line_length;
138 0 : if (pdf_is_eol_char (ch))
139 0 : writer->line_length = 0;
140 :
141 0 : writer->in_keyword = pdf_is_regular_char (ch);
142 : }
143 0 : return PDF_OK;
144 : }
145 :
146 : /* Write a single character. */
147 : static INLINE pdf_status_t
148 : write_char (pdf_token_writer_t writer, pdf_char_t ch)
149 : {
150 : pdf_size_t written;
151 0 : return write_data (writer, &ch, 1, &written);
152 : }
153 :
154 : /* Write data starting at writer->pos, incrementing writer->pos as needed. */
155 : static INLINE pdf_status_t
156 : write_data_using_pos (pdf_token_writer_t writer,
157 : const pdf_char_t *data, pdf_size_t len)
158 0 : {
159 : pdf_status_t rv;
160 : pdf_size_t written;
161 0 : if (writer->pos > len)
162 0 : return PDF_EBADDATA;
163 :
164 0 : while (writer->pos < len)
165 : {
166 0 : rv = write_data (writer, data + writer->pos, len - writer->pos,
167 : &written);
168 0 : if (rv != PDF_OK)
169 0 : return rv;
170 :
171 0 : writer->pos += written;
172 : }
173 0 : return PDF_OK;
174 : }
175 :
176 :
177 : /***** Buffered output, buffer management *****/
178 :
179 : /* Write all buffered data to the stream. */
180 : static pdf_status_t
181 : flush_buffer (pdf_token_writer_t writer)
182 0 : {
183 0 : pdf_buffer_t buf = writer->buffer;
184 : pdf_size_t len;
185 0 : while ( (len = buf->wp - buf->rp) > 0 )
186 : {
187 : pdf_size_t written;
188 : pdf_status_t rv = write_data (writer,
189 : buf->data + buf->rp,
190 0 : len, &written);
191 0 : if (rv != PDF_OK)
192 0 : return rv;
193 :
194 0 : buf->rp += written;
195 : }
196 0 : return pdf_buffer_rewind (buf);
197 : }
198 :
199 : /* Flush the buffer if there are less than 'len' bytes free. */
200 : static INLINE pdf_status_t
201 : reserve_buffer_space (pdf_token_writer_t writer, pdf_size_t len)
202 0 : {
203 0 : if (writer->buffer->wp + len > writer->buffer->size)
204 : {
205 0 : pdf_status_t rv = flush_buffer (writer);
206 0 : if (rv != PDF_OK)
207 0 : return rv;
208 :
209 0 : assert (len < writer->buffer->size);
210 0 : assert (writer->buffer->wp == 0);
211 : }
212 0 : return PDF_OK;
213 : }
214 :
215 : /* Write a character into the buffer; this assumes it will fit. */
216 : static INLINE void
217 : write_buffered_char_nocheck (pdf_token_writer_t writer, pdf_char_t ch)
218 : {
219 0 : writer->buffer->data[writer->buffer->wp++] = ch;
220 0 : if (pdf_is_eol_char(ch))
221 0 : writer->buffered_line_length = 0;
222 : else
223 0 : ++writer->buffered_line_length;
224 : }
225 :
226 : /* Write a character into the buffer. The buffer is flushed only if
227 : * there's no room to write the character. */
228 : static INLINE pdf_status_t
229 : write_buffered_char (pdf_token_writer_t writer, pdf_char_t ch)
230 0 : {
231 0 : pdf_status_t rv = reserve_buffer_space (writer, 1);
232 0 : if (rv == PDF_OK)
233 0 : write_buffered_char_nocheck (writer, ch);
234 0 : return rv;
235 : }
236 :
237 :
238 : /***** Misc. utility functions *****/
239 :
240 : /* Takes a number from 0 to 15 and returns the ASCII code for the
241 : * corresponding hexadecimal digit. */
242 : static INLINE pdf_char_t
243 : hexchar (pdf_char_t value)
244 : {
245 0 : if (value < 10)
246 0 : return 48 + value; /* '0'--'9' */
247 0 : else if (value < 16)
248 0 : return 65 + value - 10; /* 'A'--'F' */
249 0 : return 255;
250 : }
251 :
252 : /* Prepare to write a new token, adding some whitespace if necessary. */
253 : static pdf_status_t
254 : start_token (pdf_token_writer_t writer, pdf_bool_t need_wspace,
255 : pdf_size_t len)
256 0 : {
257 0 : pdf_bool_t add_wspace = (need_wspace && writer->in_keyword);
258 0 : pdf_char_t wspace_char = 32; /* space */
259 :
260 0 : if (add_wspace)
261 0 : ++len;
262 :
263 : /* If the token would make this line too long, start a new line. */
264 0 : if (writer->line_length + len > writer->max_line_length
265 : && writer->max_line_length > 0)
266 : {
267 0 : add_wspace = PDF_TRUE;
268 0 : wspace_char = 10; /* newline */
269 : }
270 :
271 0 : if (add_wspace)
272 : {
273 0 : pdf_status_t rv = write_char (writer, wspace_char);
274 0 : if (rv != PDF_OK) return rv;
275 : }
276 :
277 0 : return PDF_OK;
278 : }
279 :
280 :
281 : /***** Numeric tokens *****/
282 :
283 : /* Encode snprintf output for PDF. 'len' is the return value of snprintf.
284 : * Re-encodes bytes 0 to 'len' of writer->buffer and resets buffer->rp/wp. */
285 : static pdf_bool_t
286 : encode_buffer_number (pdf_token_writer_t writer, int len)
287 0 : {
288 0 : pdf_buffer_t buf = writer->buffer;
289 0 : if (len < 0 || len >= buf->size)
290 0 : return PDF_FALSE; /* snprintf failed, or truncated its output. */
291 :
292 0 : buf->wp = buf->rp = 0;
293 0 : while (buf->rp < len)
294 : {
295 0 : char ch = (char)buf->data[buf->rp];
296 0 : if (ch == '-')
297 : {
298 0 : ++buf->rp;
299 0 : buf->data[buf->wp++] = 45; /* '-' */
300 : }
301 0 : else if (ch >= '0' && ch <= '9')
302 : {
303 0 : ++buf->rp;
304 0 : buf->data[buf->wp++] = 48 + (ch - '0');
305 : }
306 : else
307 : {
308 : /* This should be a decimal point; check it. */
309 0 : pdf_size_t declen = strlen (writer->decimal_point);
310 : int cmp = memcmp (buf->data + buf->rp,
311 0 : writer->decimal_point, declen);
312 0 : if (cmp != 0)
313 0 : return PDF_FALSE; /* unexpected char */
314 :
315 0 : buf->rp += declen;
316 0 : buf->data[buf->wp++] = 46; /* '.' */
317 : }
318 : }
319 0 : buf->rp = 0;
320 0 : return PDF_TRUE; /* success */
321 : }
322 :
323 : static INLINE pdf_status_t
324 : write_integer_token (pdf_token_writer_t writer, pdf_token_t token)
325 : {
326 : pdf_status_t rv;
327 0 : switch (writer->stage)
328 : {
329 : case 0:
330 : {
331 0 : pdf_i32_t value = pdf_token_get_integer_value (token);
332 : int len = snprintf ((char*)writer->buffer->data,
333 0 : writer->buffer->size, "%"PRId32, value);
334 0 : if (!encode_buffer_number (writer, len)) return PDF_ERROR;
335 : }
336 0 : ++writer->stage; /* fall through */
337 : case 1:
338 0 : rv = start_token (writer, PDF_TRUE /*need_wspace*/,
339 : writer->buffer->wp);
340 0 : if (rv != PDF_OK) return rv;
341 0 : ++writer->stage; /* fall through */
342 : case 2:
343 0 : return flush_buffer (writer);
344 : default:
345 0 : return PDF_EBADDATA;
346 : }
347 : }
348 :
349 : static INLINE pdf_status_t
350 : write_real_token (pdf_token_writer_t writer, pdf_token_t token)
351 : {
352 : pdf_status_t rv;
353 0 : switch (writer->stage)
354 : {
355 : case 0:
356 : {
357 0 : pdf_buffer_t buf = writer->buffer;
358 0 : pdf_real_t value = pdf_token_get_real_value (token);
359 0 : if (isnan(value) || isinf(value))
360 0 : return PDF_EBADDATA;
361 :
362 : /* The '#' flag forces snprintf to write a decimal point. */
363 : int len = snprintf ((char*)buf->data,
364 0 : buf->size, "%#f", (double)value);
365 0 : if (!encode_buffer_number (writer, len)) return PDF_ERROR;
366 :
367 : /* strip trailing zeroes */
368 0 : while (buf->wp && buf->data[buf->wp-1] == 48 /* '0' */)
369 0 : --buf->wp;
370 : }
371 0 : ++writer->stage; /* fall through */
372 : case 1:
373 0 : rv = start_token (writer, PDF_TRUE /*need_wspace*/,
374 : writer->buffer->wp);
375 0 : if (rv != PDF_OK) return rv;
376 0 : ++writer->stage; /* fall through */
377 : case 2:
378 0 : return flush_buffer (writer);
379 : default:
380 0 : return PDF_EBADDATA;
381 : }
382 : }
383 :
384 :
385 : /***** String tokens *****/
386 :
387 : static INLINE pdf_bool_t
388 : should_escape_strchar (pdf_u32_t flags, pdf_char_t ch,
389 : pdf_bool_t quote_parens, pdf_bool_t is_utf8)
390 : {
391 0 : if (ch == 92 /* '\\' */ || ch == 13 /* CR */)
392 0 : return PDF_TRUE;
393 0 : if (ch == 40 /* '(' */ || ch == 41 /* ')' */)
394 0 : return quote_parens;
395 :
396 0 : if (flags & PDF_TOKEN_READABLE_STRINGS)
397 : {
398 0 : if (ch == 127 || (ch < 32 && ch != 10)
399 : || (ch >= 128 && !is_utf8))
400 0 : return PDF_TRUE;
401 : }
402 :
403 0 : return PDF_FALSE;
404 : }
405 :
406 : static INLINE int
407 : str_escape_len (const pdf_char_t *data, pdf_size_t len, pdf_size_t pos)
408 : {
409 0 : switch (data[pos])
410 : {
411 : /* characters with two-character escape codes */
412 : case 8:
413 : case 9:
414 : case 10:
415 : case 12:
416 : case 13:
417 : case 40:
418 : case 41:
419 : case 92:
420 0 : return 2;
421 : }
422 :
423 0 : if (data[pos] >= 0100)
424 0 : return 4; /* escaped using a backslash and 3 octal characters */
425 :
426 0 : if (pos+1 < len)
427 : {
428 0 : if (data[pos+1] >= 48 && data[pos+1] <= 57) /* '0'..'9' */
429 0 : return 4; /* need to write a 3-character octal number */
430 : }
431 :
432 0 : return (data[pos] >= 010) ? 3 : 2;
433 : }
434 :
435 : static void
436 : scan_string (pdf_token_writer_t writer, pdf_u32_t flags,
437 : const pdf_char_t *data, pdf_size_t len, pdf_bool_t *use_hex)
438 0 : {
439 : /* Match parentheses, and determine the portion of the string
440 : * in which they should be quoted. */
441 0 : writer->paren_quoting_start = 0;
442 0 : writer->paren_quoting_end = len;
443 : pdf_size_t i, j;
444 0 : for (i = 0; i < len; ++i)
445 : {
446 0 : if (data[i] == 40) /* '(' */
447 : {
448 0 : for (j = writer->paren_quoting_end - 1; j > i; --j)
449 : {
450 : /* find a matching ')' */
451 0 : if (data[j] == 41)
452 : {
453 0 : writer->paren_quoting_end = j;
454 0 : writer->paren_quoting_start = i + 1;
455 0 : break;
456 : }
457 : }
458 : }
459 : }
460 :
461 : /* Determine the size of the escaped string. */
462 0 : writer->utf8 = (flags & PDF_TOKEN_READABLE_STRINGS)
463 : && (u8_check (data, len) == NULL);
464 0 : pdf_size_t enc_bytes = 0;
465 0 : for (i = 0; i < len; ++i)
466 : {
467 : pdf_bool_t quote_parens = (i >= writer->paren_quoting_start
468 0 : && i < writer->paren_quoting_end);
469 0 : if (should_escape_strchar (flags, data[i], quote_parens, writer->utf8))
470 0 : enc_bytes += str_escape_len (data, len, i);
471 : else
472 0 : ++enc_bytes;
473 : }
474 0 : *use_hex = (enc_bytes > len*2);
475 0 : }
476 :
477 : static INLINE pdf_status_t
478 : write_string_char (pdf_token_writer_t writer, pdf_u32_t flags,
479 : const pdf_char_t *data, pdf_char_t len,
480 : pdf_size_t pos)
481 : {
482 0 : assert (len > 0);
483 : pdf_status_t rv;
484 0 : const pdf_char_t *output = data + pos;
485 0 : pdf_size_t outlen = 1;
486 0 : pdf_char_t esc[4] = {92 /* '\\' */, 0, 0, 0};
487 :
488 0 : pdf_char_t ch = data[pos];
489 : pdf_bool_t quote_parens = (pos >= writer->paren_quoting_start
490 0 : && pos < writer->paren_quoting_end);
491 0 : if (should_escape_strchar (flags, ch, quote_parens, writer->utf8))
492 : {
493 : /* escape the character */
494 0 : output = esc;
495 0 : outlen = 2;
496 0 : switch (ch)
497 : {
498 0 : case 8: esc[1] = 98; break; /* 'b' */
499 0 : case 9: esc[1] = 116; break; /* 't' */
500 0 : case 10: esc[1] = 110; break; /* 'n' */
501 0 : case 12: esc[1] = 102; break; /* 'f' */
502 0 : case 13: esc[1] = 114; break; /* 'r' */
503 : case 40: /* '('; fall through */
504 : case 41: /* ')'; fall through */
505 : case 92: /* '\\' */
506 0 : esc[1] = ch;
507 : break;
508 : default: /* use an octal escape */
509 : {
510 : pdf_size_t digits;
511 0 : pdf_char_t nextch = (pos+1 < len) ? data[pos+1] : 0;
512 0 : if (nextch >= 48 && nextch <= 57) /* '0'..'9' */
513 0 : digits = 3; /* must use 3 octal characters */
514 0 : else if (ch > 0100) digits = 3;
515 0 : else if (ch > 010) digits = 2;
516 0 : else digits = 1;
517 :
518 0 : outlen = 1;
519 0 : switch (digits)
520 : {
521 : /* fall through each case */
522 0 : case 3: esc[outlen++] = hexchar (ch / 0100);
523 0 : case 2: esc[outlen++] = hexchar ((ch % 0100) / 010);
524 0 : case 1: esc[outlen++] = hexchar (ch % 010);
525 : }
526 : }
527 : }
528 : }
529 :
530 : /* If the line will be too long, split it (the length cannot be equal to
531 : * the maximum, since this would leave no room for the backslash). */
532 0 : if (writer->max_line_length > 0 && !pdf_is_eol_char (output[0])
533 : && writer->buffered_line_length + outlen >= writer->max_line_length)
534 : {
535 0 : rv = reserve_buffer_space (writer, 2);
536 0 : if (rv != PDF_OK) return rv;
537 : write_buffered_char_nocheck (writer, 92); /* '\\' */
538 : write_buffered_char_nocheck (writer, 10); /* newline */
539 0 : assert (writer->buffered_line_length == 0);
540 : }
541 :
542 0 : rv = reserve_buffer_space (writer, outlen);
543 0 : if (rv == PDF_OK)
544 : {
545 : pdf_size_t i;
546 0 : for (i = 0; i < outlen; ++i)
547 0 : write_buffered_char_nocheck (writer, output[i]);
548 : }
549 0 : return rv;
550 : }
551 :
552 : static INLINE pdf_status_t
553 : write_string_token (pdf_token_writer_t writer, pdf_u32_t flags,
554 : pdf_token_t token)
555 0 : {
556 : pdf_status_t rv;
557 0 : const pdf_char_t *data = pdf_token_get_string_data (token);
558 0 : pdf_size_t size = pdf_token_get_string_size (token);
559 :
560 0 : switch (writer->stage)
561 : {
562 : case 0:
563 : {
564 0 : pdf_bool_t use_hex = (flags & PDF_TOKEN_HEX_STRINGS);
565 0 : if (!use_hex)
566 0 : scan_string (writer, flags, data, size, &use_hex);
567 :
568 0 : if (use_hex)
569 0 : goto hexstring_start;
570 : }
571 0 : ++writer->stage; /* fall through */
572 : case 1:
573 : {
574 : /* Passing a correct length to start_token isn't important
575 : * since we can split the string across multiple lines. */
576 0 : pdf_size_t dummy_len = PDF_MIN(20, 2 + size);
577 0 : rv = start_token (writer, PDF_FALSE /*need_wspace*/, dummy_len);
578 0 : if (rv != PDF_OK) return rv;
579 : }
580 :
581 0 : pdf_buffer_rewind (writer->buffer);
582 0 : writer->buffered_line_length = writer->line_length;
583 : write_buffered_char_nocheck (writer, 40 /* '(' */);
584 0 : writer->pos = 0;
585 0 : ++writer->stage; /* fall through */
586 : case 2:
587 0 : while (writer->pos < size)
588 : {
589 0 : rv = write_string_char (writer, flags, data, size, writer->pos);
590 0 : if (rv != PDF_OK) return rv;
591 :
592 0 : ++writer->pos;
593 : }
594 0 : ++writer->stage; /* fall through */
595 : case 3:
596 0 : rv = write_buffered_char (writer, 41 /* ')' */);
597 0 : if (rv != PDF_OK) return rv;
598 0 : ++writer->stage; /* fall through */
599 : case 4:
600 0 : return flush_buffer (writer);
601 :
602 : /*** hex strings ***/
603 0 : hexstring_start:
604 0 : writer->stage = 101;
605 : case 101:
606 : {
607 0 : pdf_size_t dummy_len = PDF_MIN(20, 2 + size*2);
608 0 : rv = start_token (writer, PDF_FALSE /*need_wspace*/, dummy_len);
609 0 : if (rv != PDF_OK) return rv;
610 : }
611 :
612 0 : pdf_buffer_rewind (writer->buffer);
613 0 : writer->buffered_line_length = writer->line_length;
614 : write_buffered_char_nocheck (writer, 60 /* '<' */);
615 0 : writer->pos = 0;
616 0 : ++writer->stage; /* fall through */
617 : case 102:
618 0 : while (writer->pos < size)
619 : {
620 : /* If this line would be too long, start a new one. */
621 0 : if (writer->buffered_line_length + 2 > writer->max_line_length
622 : && writer->max_line_length > 0)
623 : {
624 0 : rv = write_buffered_char (writer, 10); /* newline */
625 0 : if (rv != PDF_OK) return rv;
626 0 : assert (writer->buffered_line_length == 0);
627 : }
628 :
629 0 : pdf_char_t ch = data[writer->pos];
630 0 : rv = reserve_buffer_space (writer, 2);
631 0 : if (rv != PDF_OK) return rv;
632 :
633 0 : write_buffered_char_nocheck (writer, hexchar (ch / 16));
634 0 : if (writer->pos == size-1 && (ch%16) == 0)
635 : ; /* don't write a final 0 */
636 : else
637 0 : write_buffered_char_nocheck (writer, hexchar (ch % 16));
638 0 : ++writer->pos;
639 : }
640 0 : ++writer->stage; /* fall through */
641 : case 103:
642 0 : rv = write_buffered_char (writer, 62 /* '>' */);
643 0 : if (rv != PDF_OK) return rv;
644 0 : ++writer->stage; /* fall through */
645 : case 104:
646 0 : return flush_buffer (writer);
647 : default:
648 0 : return PDF_EBADDATA;
649 : }
650 : }
651 :
652 :
653 : /***** Other tokens *****/
654 :
655 : static INLINE pdf_status_t
656 : should_escape_namechar (pdf_u32_t flags, pdf_char_t ch, pdf_bool_t *escape)
657 : {
658 0 : if (!ch)
659 0 : return PDF_EBADDATA;
660 :
661 0 : *escape = !pdf_is_regular_char (ch);
662 0 : if (flags & PDF_TOKEN_NO_NAME_ESCAPES)
663 : {
664 0 : if (*escape)
665 0 : return PDF_EBADDATA;
666 : }
667 : else
668 : {
669 0 : *escape = *escape || ch == 35 /* '#' */
670 : || ch < 33 || ch >= 127;
671 : }
672 0 : return PDF_OK;
673 : }
674 :
675 : static INLINE pdf_status_t
676 : write_name_token (pdf_token_writer_t writer, pdf_u32_t flags,
677 : pdf_token_t token)
678 : {
679 : pdf_status_t rv;
680 0 : pdf_size_t size = pdf_token_get_name_size (token);
681 0 : const pdf_char_t *data = pdf_token_get_name_data (token);
682 0 : switch (writer->stage)
683 : {
684 : case 0:
685 : /* Validate the name; also calculate the encoded size
686 : * and store it in ->pos temporarily. */
687 0 : writer->pos = 1 + size;
688 : {
689 : pdf_size_t i;
690 0 : for (i = 0; i < size; ++i)
691 : {
692 : pdf_bool_t escape;
693 0 : rv = should_escape_namechar (flags, data[i], &escape);
694 0 : if (rv != PDF_OK) return rv; /* bad name */
695 :
696 0 : if (escape)
697 0 : writer->pos += 2; /* 2 hex characters */
698 : }
699 : }
700 0 : pdf_buffer_rewind (writer->buffer);
701 : write_buffered_char_nocheck (writer, 47 /* '/' */);
702 0 : ++writer->stage; /* fall through */
703 : case 1:
704 0 : rv = start_token (writer, PDF_FALSE /*need_wspace*/,
705 : writer->pos /* encoded token length */);
706 0 : if (rv != PDF_OK) return rv;
707 :
708 0 : writer->pos = 0;
709 0 : ++writer->stage; /* fall through */
710 : case 2:
711 0 : while (writer->pos < size)
712 : {
713 : pdf_bool_t escape;
714 0 : pdf_char_t ch = data[writer->pos];
715 0 : rv = should_escape_namechar (flags, ch, &escape);
716 0 : if (rv != PDF_OK) return rv; /* bad name */
717 :
718 0 : if (escape)
719 : {
720 0 : rv = reserve_buffer_space (writer, 3);
721 0 : if (rv != PDF_OK) return rv;
722 :
723 : write_buffered_char_nocheck (writer, 35 /* '#' */);
724 0 : write_buffered_char_nocheck (writer, hexchar (ch / 16));
725 0 : write_buffered_char_nocheck (writer, hexchar (ch % 16));
726 : }
727 : else
728 : {
729 0 : rv = write_buffered_char (writer, ch);
730 0 : if (rv != PDF_OK) return rv;
731 : }
732 0 : ++writer->pos;
733 : }
734 0 : ++writer->stage; /* fall through */
735 : case 3:
736 0 : return flush_buffer (writer);
737 : default:
738 0 : return PDF_EBADDATA;
739 : }
740 : }
741 :
742 : static INLINE pdf_status_t
743 : write_keyword_token (pdf_token_writer_t writer, pdf_token_t token)
744 : {
745 0 : const pdf_char_t *data = pdf_token_get_keyword_data (token);
746 0 : pdf_size_t size = pdf_token_get_keyword_size (token);
747 : pdf_status_t rv;
748 0 : switch (writer->stage)
749 : {
750 : case 0:
751 0 : if (memchr (data, 0, size))
752 0 : return PDF_EBADDATA; /* data contains a null byte */
753 0 : ++writer->stage; /* fall through */
754 : case 1:
755 0 : rv = start_token (writer, PDF_TRUE /*need_wspace*/, size);
756 0 : if (rv != PDF_OK) return rv;
757 :
758 0 : writer->pos = 0;
759 0 : ++writer->stage; /* fall through */
760 : case 2:
761 0 : return write_data_using_pos (writer,
762 : pdf_token_get_keyword_data (token),
763 : size);
764 : default:
765 0 : return PDF_EBADDATA;
766 : }
767 : }
768 :
769 : static INLINE pdf_status_t
770 : write_comment_token (pdf_token_writer_t writer, pdf_token_t token)
771 : {
772 0 : const pdf_char_t *data = pdf_token_get_comment_data (token);
773 0 : pdf_size_t size = pdf_token_get_comment_size (token);
774 : pdf_status_t rv;
775 0 : switch (writer->stage)
776 : {
777 : case 0:
778 : {
779 : /* A comment can't span multiple lines. */
780 : pdf_size_t i;
781 0 : for (i = 0; i < size; ++i)
782 : {
783 0 : if (pdf_is_eol_char(data[i]))
784 0 : return PDF_EBADDATA;
785 : }
786 : }
787 0 : ++writer->stage; /* fall through */
788 : case 1:
789 0 : rv = start_token (writer, PDF_FALSE /*need_wspace*/, size+1);
790 0 : if (rv != PDF_OK) return rv;
791 0 : ++writer->stage; /* fall through */
792 : case 2:
793 0 : rv = write_char (writer, 37 /* '%' */);
794 0 : if (rv != PDF_OK) return rv;
795 :
796 0 : writer->pos = 0;
797 0 : ++writer->stage; /* fall through */
798 : case 3:
799 0 : rv = write_data_using_pos (writer, data, size);
800 0 : if (rv != PDF_OK) return rv;
801 0 : ++writer->stage; /* fall through */
802 : case 4:
803 0 : return write_char (writer, 10 /* '\n' */);
804 : default:
805 0 : return PDF_EBADDATA;
806 : }
807 : }
808 :
809 : static INLINE pdf_status_t
810 : write_valueless_token (pdf_token_writer_t writer,
811 : pdf_char_t ch, pdf_size_t len)
812 0 : {
813 0 : pdf_char_t buf[2] = {ch,ch};
814 : pdf_status_t rv;
815 0 : assert (len == 1 || len == 2);
816 :
817 0 : switch (writer->stage)
818 : {
819 : case 0:
820 0 : rv = start_token (writer, PDF_FALSE /*need_wspace*/, len);
821 0 : if (rv != PDF_OK) return rv;
822 :
823 0 : writer->pos = 0;
824 0 : ++writer->stage; /* fall through */
825 : case 1:
826 0 : return write_data_using_pos (writer, buf, len);
827 : default:
828 0 : return PDF_EBADDATA;
829 : }
830 : }
831 :
832 :
833 : /***** Token dispatching *****/
834 :
835 : pdf_status_t
836 : pdf_token_write (pdf_token_writer_t writer, pdf_u32_t flags, pdf_token_t token)
837 0 : {
838 : pdf_status_t rv;
839 0 : switch (pdf_token_get_type (token))
840 : {
841 : case PDF_TOKEN_INTEGER:
842 0 : rv = write_integer_token (writer, token);
843 0 : break;
844 : case PDF_TOKEN_REAL:
845 0 : rv = write_real_token (writer, token);
846 0 : break;
847 : case PDF_TOKEN_STRING:
848 0 : rv = write_string_token (writer, flags, token);
849 0 : break;
850 : case PDF_TOKEN_NAME:
851 0 : rv = write_name_token (writer, flags, token);
852 0 : break;
853 : case PDF_TOKEN_KEYWORD:
854 0 : rv = write_keyword_token (writer, token);
855 0 : break;
856 : case PDF_TOKEN_COMMENT:
857 0 : rv = write_comment_token (writer, token);
858 0 : break;
859 : case PDF_TOKEN_DICT_START:
860 0 : rv = write_valueless_token (writer, 60 /* '<' */, 2);
861 0 : break;
862 : case PDF_TOKEN_DICT_END:
863 0 : rv = write_valueless_token (writer, 62 /* '>' */, 2);
864 0 : break;
865 : case PDF_TOKEN_ARRAY_START:
866 0 : rv = write_valueless_token (writer, 91 /* '[' */, 1);
867 0 : break;
868 : case PDF_TOKEN_ARRAY_END:
869 0 : rv = write_valueless_token (writer, 93 /* ']' */, 1);
870 0 : break;
871 : case PDF_TOKEN_PROC_START:
872 0 : rv = write_valueless_token (writer, 123 /* '{' */, 1);
873 0 : break;
874 : case PDF_TOKEN_PROC_END:
875 0 : rv = write_valueless_token (writer, 125 /* '}' */, 1);
876 0 : break;
877 : default:
878 0 : assert (0);
879 : return PDF_ERROR;
880 : }
881 :
882 0 : if (rv == PDF_OK)
883 0 : writer->stage = 0;
884 0 : return rv;
885 : }
886 :
887 : /* End of pdf-token-writer.c */
|