1 /*
2 * This file is part of darktable,
3 * Copyright (C) 2015-2020 darktable developers.
4 *
5 * darktable is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * darktable is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /*
20 * this is a simple PDF writer, capable of creating multi page PDFs with embedded images.
21 * it is NOT meant to be a full fledged PDF library, and shall never turn into something like that!
22 */
23
24 // add the following define to compile this into a standalone test program:
25 // #define STANDALONE
26 // or use
27 // gcc -W -Wall -std=c99 -lz -lm `pkg-config --cflags --libs glib-2.0` -g -O3 -fopenmp -DSTANDALONE -o darktable-pdf pdf.c
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #define _XOPEN_SOURCE 700
34 #include <errno.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <strings.h>
39 #include <time.h>
40 #include <zlib.h>
41
42 #include <glib/gstdio.h>
43
44 #ifdef STANDALONE
45 #define PACKAGE_STRING "darktable pdf library"
46 #else
47 #define PACKAGE_STRING darktable_package_string
48 #endif
49
50 #include "pdf.h"
51 #include "common/math.h"
52 #include "common/utility.h"
53
54 #define SKIP_SPACES(s) {while(*(s) == ' ')(s)++;}
55
56 // puts the length as described in str as pdf points into *length
57 // returns 0 on error
58 // a length has a number, followed by a unit if it's != 0.0
dt_pdf_parse_length(const char * str,float * length)59 int dt_pdf_parse_length(const char *str, float *length)
60 {
61 int res = 0;
62 char *nptr, *endptr;
63
64 if(str == NULL || length == NULL)
65 return 0;
66
67 SKIP_SPACES(str);
68
69 nptr = g_strdelimit(g_strdup(str), ",", '.');
70
71 *length = g_ascii_strtod(nptr, &endptr);
72
73 if(endptr == NULL || errno == ERANGE)
74 goto end;
75
76 // 0 is 0 is 0, why should we care about the unit?
77 if(*length == 0.0 && nptr != endptr)
78 {
79 res = 1;
80 goto end;
81 }
82
83 // we don't want NAN, INF or parse errors (== 0.0)
84 if(!isnormal(*length))
85 goto end;
86
87 SKIP_SPACES(endptr);
88
89 for(int i = 0; dt_pdf_units[i].name; i++)
90 {
91 if(!g_strcmp0(endptr, dt_pdf_units[i].name))
92 {
93 *length *= dt_pdf_units[i].factor;
94 res = 1;
95 break;
96 }
97 }
98
99 end:
100 g_free(nptr);
101 return res;
102 }
103
104 // a paper size has 2 numbers, separated by 'x' or '*' and a unit, either one per number or one in the end (for both)
105 // <n> <u>? [x|*] <n> <u>
106 // alternatively it could be a well defined format
dt_pdf_parse_paper_size(const char * str,float * width,float * height)107 int dt_pdf_parse_paper_size(const char *str, float *width, float *height)
108 {
109 int res = 0;
110 gboolean width_has_unit = FALSE;
111 char *ptr, *nptr, *endptr;
112
113 if(str == NULL || width == NULL || height == NULL)
114 return 0;
115
116 // first check if this is a well known size
117 for(int i = 0; dt_pdf_paper_sizes[i].name; i++)
118 {
119 if(!strcasecmp(str, dt_pdf_paper_sizes[i].name))
120 {
121 *width = dt_pdf_paper_sizes[i].width;
122 *height = dt_pdf_paper_sizes[i].height;
123 return 1;
124 }
125 }
126
127 ptr = nptr = g_strdelimit(g_strdup(str), ",", '.');
128
129 // width
130 SKIP_SPACES(nptr);
131
132 *width = g_ascii_strtod(nptr, &endptr);
133
134 if(endptr == NULL || *endptr == '\0' || errno == ERANGE || !isnormal(*width))
135 goto end;
136
137 nptr = endptr;
138
139 // unit?
140 SKIP_SPACES(nptr);
141
142 for(int i = 0; dt_pdf_units[i].name; i++)
143 {
144 if(g_str_has_prefix(nptr, dt_pdf_units[i].name))
145 {
146 *width *= dt_pdf_units[i].factor;
147 width_has_unit = TRUE;
148 nptr += strlen(dt_pdf_units[i].name);
149 break;
150 }
151 }
152
153 // x
154 SKIP_SPACES(nptr);
155
156 if(*nptr != 'x' && *nptr != '*')
157 goto end;
158
159 nptr++;
160
161 // height
162 SKIP_SPACES(nptr);
163
164 *height = g_ascii_strtod(nptr, &endptr);
165
166 if(endptr == NULL || *endptr == '\0' || errno == ERANGE || !isnormal(*height))
167 goto end;
168
169 nptr = endptr;
170
171 // unit
172 SKIP_SPACES(nptr);
173
174 for(int i = 0; dt_pdf_units[i].name; i++)
175 {
176 if(!g_strcmp0(nptr, dt_pdf_units[i].name))
177 {
178 *height *= dt_pdf_units[i].factor;
179 if(width_has_unit == FALSE)
180 *width *= dt_pdf_units[i].factor;
181 res = 1;
182 break;
183 }
184 }
185
186 end:
187 g_free(ptr);
188 return res;
189 }
190
191 #undef SKIP_SPACES
192
193
194 static const char *stream_encoder_filters[] = {"/ASCIIHexDecode", "/FlateDecode"};
195
_pdf_set_offset(dt_pdf_t * pdf,int id,size_t offset)196 static void _pdf_set_offset(dt_pdf_t *pdf, int id, size_t offset)
197 {
198 id--; // object ids start at 1
199 if(id >= pdf->n_offsets)
200 {
201 pdf->n_offsets = MAX(pdf->n_offsets * 2, id);
202 pdf->offsets = realloc(pdf->offsets, sizeof(size_t) * pdf->n_offsets);
203 }
204 pdf->offsets[id] = offset;
205 }
206
dt_pdf_start(const char * filename,float width,float height,float dpi,dt_pdf_stream_encoder_t default_encoder)207 dt_pdf_t *dt_pdf_start(const char *filename, float width, float height, float dpi, dt_pdf_stream_encoder_t default_encoder)
208 {
209 dt_pdf_t *pdf = calloc(1, sizeof(dt_pdf_t));
210 if(!pdf) return NULL;
211
212 pdf->fd = g_fopen(filename, "wb");
213 if(!pdf->fd)
214 {
215 free(pdf);
216 return NULL;
217 }
218
219 pdf->page_width = width;
220 pdf->page_height = height;
221 pdf->dpi = dpi;
222 pdf->default_encoder = default_encoder;
223 // object counting starts at 1, and the first 2 are reserved for the document catalog + pages dictionary
224 pdf->next_id = 3;
225 pdf->next_image = 0;
226
227 pdf->n_offsets = 4;
228 pdf->offsets = calloc(pdf->n_offsets, sizeof(size_t));
229
230 size_t bytes_written = 0;
231
232 // file header
233 // pdf specs encourage to put 4 binary bytes in a comment
234 bytes_written += fprintf(pdf->fd, "%%PDF-1.3\n\xde\xad\xbe\xef\n");
235
236 // document catalog
237 _pdf_set_offset(pdf, 1, bytes_written);
238 bytes_written += fprintf(pdf->fd,
239 "1 0 obj\n"
240 "<<\n"
241 "/Pages 2 0 R\n"
242 "/Type /Catalog\n"
243 ">>\n"
244 "endobj\n"
245 );
246
247 pdf->bytes_written += bytes_written;
248
249 return pdf;
250 }
251
252 // TODO: maybe OpenMP-ify, it's quite fast already (the fwrite is the slowest part), but wouldn't hurt
_pdf_stream_encoder_ASCIIHex(dt_pdf_t * pdf,const unsigned char * data,size_t len)253 static size_t _pdf_stream_encoder_ASCIIHex(dt_pdf_t *pdf, const unsigned char *data, size_t len)
254 {
255 const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
256
257 char buf[512]; // keep this a multiple of 2!
258
259 for(size_t i = 0; i < len; i++)
260 {
261 const int hi = data[i] >> 4;
262 const int lo = data[i] & 15;
263 buf[(2 * i) % sizeof(buf)] = hex[hi];
264 buf[(2 * i + 1) % sizeof(buf)] = hex[lo];
265 if((i + 1) % (sizeof(buf) / 2) == 0 || (i + 1) == len)
266 fwrite(buf, 1, (i % (sizeof(buf) / 2) + 1) * 2, pdf->fd);
267 }
268 return len * 2;
269 }
270
271 // using zlib we get quite small files, but it's slow
_pdf_stream_encoder_Flate(dt_pdf_t * pdf,const unsigned char * data,size_t len)272 static size_t _pdf_stream_encoder_Flate(dt_pdf_t *pdf, const unsigned char *data, size_t len)
273 {
274 int result;
275 uLongf destLen = compressBound(len);
276 unsigned char *buffer = (unsigned char *)malloc(destLen);
277
278 result = compress(buffer, &destLen, data, len);
279
280 if(result != Z_OK)
281 {
282 free(buffer);
283 return 0;
284 }
285
286 fwrite(buffer, 1, destLen, pdf->fd);
287
288 free(buffer);
289 return destLen;
290 }
291
_pdf_write_stream(dt_pdf_t * pdf,dt_pdf_stream_encoder_t encoder,const unsigned char * data,size_t len)292 static size_t _pdf_write_stream(dt_pdf_t *pdf, dt_pdf_stream_encoder_t encoder, const unsigned char *data, size_t len)
293 {
294 size_t stream_size = 0;
295 switch(encoder)
296 {
297 case DT_PDF_STREAM_ENCODER_ASCII_HEX:
298 stream_size = _pdf_stream_encoder_ASCIIHex(pdf, data, len);
299 break;
300 case DT_PDF_STREAM_ENCODER_FLATE:
301 stream_size = _pdf_stream_encoder_Flate(pdf, data, len);
302 break;
303 }
304 pdf->bytes_written += stream_size;
305 return stream_size;
306 }
307
dt_pdf_add_icc(dt_pdf_t * pdf,const char * filename)308 int dt_pdf_add_icc(dt_pdf_t *pdf, const char *filename)
309 {
310 size_t len;
311 unsigned char *data = (unsigned char *)dt_read_file(filename, &len);
312 if (data)
313 {
314 int icc_id = dt_pdf_add_icc_from_data(pdf, data, len);
315 free(data);
316 return icc_id;
317 }
318 else
319 return 0;
320 }
321
dt_pdf_add_icc_from_data(dt_pdf_t * pdf,const unsigned char * data,size_t size)322 int dt_pdf_add_icc_from_data(dt_pdf_t *pdf, const unsigned char *data, size_t size)
323 {
324 int icc_id = pdf->next_id++;
325 int length_id = pdf->next_id++;
326 size_t bytes_written = 0;
327
328 // length of the stream
329 _pdf_set_offset(pdf, icc_id, pdf->bytes_written + bytes_written);
330 bytes_written += fprintf(pdf->fd,
331 "%d 0 obj\n"
332 "<<\n"
333 "/N 3\n" // should we ever support CMYK profiles then this has to be set to 4 for those
334 "/Alternate /DeviceRGB\n"
335 "/Length %d 0 R\n"
336 "/Filter [ /ASCIIHexDecode ]\n"
337 ">>\n"
338 "stream\n",
339 icc_id, length_id
340 );
341
342 size_t stream_size = _pdf_stream_encoder_ASCIIHex(pdf, data, size);
343 bytes_written += stream_size;
344
345 bytes_written += fprintf(pdf->fd,
346 "\n"
347 "endstream\n"
348 "endobj\n"
349 );
350
351 // length of the stream
352 _pdf_set_offset(pdf, length_id, pdf->bytes_written + bytes_written);
353 bytes_written += fprintf(pdf->fd, "%d 0 obj\n"
354 "%zu\n"
355 "endobj\n",
356 length_id, stream_size);
357
358 pdf->bytes_written += bytes_written;
359
360 return icc_id;
361 }
362
363 // this adds an image to the pdf file and returns the info needed to reference it later.
364 // if icc_id is 0 then we suppose the pixel data to be in output device space, otherwise the ICC profile object is referenced.
365 // if image == NULL only the outline can be shown later
dt_pdf_add_image(dt_pdf_t * pdf,const unsigned char * image,int width,int height,int bpp,int icc_id,float border)366 dt_pdf_image_t *dt_pdf_add_image(dt_pdf_t *pdf, const unsigned char *image, int width, int height, int bpp, int icc_id, float border)
367 {
368 size_t stream_size = 0;
369 size_t bytes_written = 0;
370
371 dt_pdf_image_t *pdf_image = calloc(1, sizeof(dt_pdf_image_t));
372 if(!pdf_image) return NULL;
373
374 pdf_image->width = width;
375 pdf_image->height = height;
376 pdf_image->outline_mode = (image == NULL);
377 // no need to do fancy math here:
378 pdf_image->bb_x = border;
379 pdf_image->bb_y = border;
380 pdf_image->bb_width = pdf->page_width - (2 * border);
381 pdf_image->bb_height = pdf->page_height - (2 * border);
382
383 // just draw outlines if the image is missing
384 if(pdf_image->outline_mode) return pdf_image;
385
386 pdf_image->object_id = pdf->next_id++;
387 pdf_image->name_id = pdf->next_image++;
388
389 int length_id = pdf->next_id++;
390
391 // the image
392 //start
393 _pdf_set_offset(pdf, pdf_image->object_id, pdf->bytes_written + bytes_written);
394 bytes_written += fprintf(pdf->fd,
395 "%d 0 obj\n"
396 "<<\n"
397 "/Type /XObject\n"
398 "/Subtype /Image\n"
399 "/Name /Im%d\n"
400 "/Filter [ %s ]\n"
401 "/Width %d\n"
402 "/Height %d\n",
403 pdf_image->object_id, pdf_image->name_id, stream_encoder_filters[pdf->default_encoder], width, height
404 );
405 // As I understand it in the printing case DeviceRGB (==> icc_id = 0) is enough since the pixel data is in the device space then.
406 if(icc_id > 0)
407 bytes_written += fprintf(pdf->fd, "/ColorSpace [ /ICCBased %d 0 R ]\n", icc_id);
408 else
409 bytes_written += fprintf(pdf->fd, "/ColorSpace /DeviceRGB\n");
410 bytes_written += fprintf(pdf->fd,
411 "/BitsPerComponent %d\n"
412 "/Intent /Perceptual\n" // TODO: allow setting it from the outside
413 "/Length %d 0 R\n"
414 ">>\n"
415 "stream\n",
416 bpp, length_id
417 );
418
419 // the stream
420 stream_size = _pdf_write_stream(pdf, pdf->default_encoder, image, (size_t)3 * (bpp / 8) * width * height);
421 if(stream_size == 0)
422 {
423 free(pdf_image);
424 return NULL;
425 }
426 bytes_written += stream_size;
427
428 //end
429 bytes_written += fprintf(pdf->fd,
430 "\n"
431 "endstream\n"
432 "endobj\n"
433 );
434
435 // length of the last stream
436 _pdf_set_offset(pdf, length_id, pdf->bytes_written + bytes_written);
437 bytes_written += fprintf(pdf->fd, "%d 0 obj\n"
438 "%zu\n"
439 "endobj\n",
440 length_id, stream_size);
441
442 pdf->bytes_written += bytes_written;
443 pdf_image->size = bytes_written;
444
445 return pdf_image;
446 }
447
dt_pdf_add_page(dt_pdf_t * pdf,dt_pdf_image_t ** images,int n_images)448 dt_pdf_page_t *dt_pdf_add_page(dt_pdf_t *pdf, dt_pdf_image_t **images, int n_images)
449 {
450 dt_pdf_page_t *pdf_page = calloc(1, sizeof(dt_pdf_page_t));
451 if(!pdf_page) return NULL;
452 pdf_page->object_id = pdf->next_id++;
453 int content_id = pdf->next_id++;
454 int length_id = pdf->next_id++;
455 size_t stream_size = 0, bytes_written = 0;
456
457 // the page object
458 _pdf_set_offset(pdf, pdf_page->object_id, pdf->bytes_written + bytes_written);
459 bytes_written += fprintf(pdf->fd,
460 "%d 0 obj\n"
461 "<<\n"
462 "/Type /Page\n"
463 "/Parent 2 0 R\n"
464 "/Resources <<\n"
465 "/XObject <<",
466 pdf_page->object_id
467 );
468 for(int i = 0; i < n_images; i++)
469 bytes_written += fprintf(pdf->fd, "/Im%d %d 0 R\n", images[i]->name_id, images[i]->object_id);
470 bytes_written += fprintf(pdf->fd,
471 ">>\n"
472 "/ProcSet [ /PDF /Text /ImageC ] >>\n"
473 "/MediaBox [0 0 %d %d]\n"
474 "/Contents %d 0 R\n"
475 ">>\n"
476 "endobj\n",
477 (int)(pdf->page_width + 0.5), (int)(pdf->page_height + 0.5), content_id
478 );
479
480 // page content
481 _pdf_set_offset(pdf, content_id, pdf->bytes_written + bytes_written);
482 bytes_written += fprintf(pdf->fd,
483 "%d 0 obj\n"
484 "<<\n"
485 "/Length %d 0 R\n"
486 ">>\n"
487 "stream\n",
488 content_id, length_id
489 );
490
491 // the stream -- we need its size in the length object
492 // we want the image printed with at least the given DPI, scaling it down to fit the page if it is too big
493 gboolean portrait_page = pdf->page_width < pdf->page_height;
494
495 for(int i = 0; i < n_images; i++)
496 {
497 // fit the image into the bounding box that comes with the image
498 float scale_x, scale_y, translate_x, translate_y;
499 float width, height;
500 gboolean portrait_image = images[i]->width < images[i]->height;
501 gboolean rotate_to_fit = images[i]->rotate_to_fit && (portrait_page != portrait_image);
502 if(rotate_to_fit)
503 {
504 width = images[i]->height;
505 height = images[i]->width;
506 }
507 else
508 {
509 width = images[i]->width;
510 height = images[i]->height;
511 }
512
513 float image_aspect_ratio = width / height;
514 float bb_aspect_ratio = images[i]->bb_width / images[i]->bb_height;
515
516 if(image_aspect_ratio <= bb_aspect_ratio)
517 {
518 // scale to fit height
519 float height_in_point = (height / pdf->dpi) * 72.0;
520 scale_y = MIN(images[i]->bb_height, height_in_point);
521 scale_x = scale_y * image_aspect_ratio;
522 }
523 else
524 {
525 // scale to fit width
526 float width_in_point = (width / pdf->dpi) * 72.0;
527 scale_x = MIN(images[i]->bb_width, width_in_point);
528 scale_y = scale_x / image_aspect_ratio;
529 }
530
531 // center inside image's bounding box
532 translate_x = images[i]->bb_x + 0.5 * (images[i]->bb_width - scale_x);
533 translate_y = images[i]->bb_y + 0.5 * (images[i]->bb_height - scale_y);
534
535 if(rotate_to_fit && !images[i]->outline_mode)
536 {
537 float tmp = scale_x;
538 scale_x = scale_y;
539 scale_y = tmp;
540 translate_x += scale_y;
541 }
542
543 // unfortunately regular fprintf honors the decimal separator as set by the current locale,
544 // we want '.' in all cases though.
545 char translate_x_str[G_ASCII_DTOSTR_BUF_SIZE];
546 char translate_y_str[G_ASCII_DTOSTR_BUF_SIZE];
547 char scale_x_str[G_ASCII_DTOSTR_BUF_SIZE];
548 char scale_y_str[G_ASCII_DTOSTR_BUF_SIZE];
549
550 g_ascii_dtostr(translate_x_str, G_ASCII_DTOSTR_BUF_SIZE, translate_x);
551 g_ascii_dtostr(translate_y_str, G_ASCII_DTOSTR_BUF_SIZE, translate_y);
552 g_ascii_dtostr(scale_x_str, G_ASCII_DTOSTR_BUF_SIZE, scale_x);
553 g_ascii_dtostr(scale_y_str, G_ASCII_DTOSTR_BUF_SIZE, scale_y);
554
555 if(images[i]->outline_mode)
556 {
557 // instead of drawign the image we just draw the outlines
558 stream_size += fprintf(pdf->fd,
559 "q\n"
560 "[4 6] 0 d\n"
561 "%s %s %s %s re\n"
562 "S\n"
563 "Q\n",
564 translate_x_str, translate_y_str, scale_x_str, scale_y_str
565 );
566 }
567 else
568 {
569 stream_size += fprintf(pdf->fd,
570 "q\n"
571 "1 0 0 1 %s %s cm\n", // translate
572 translate_x_str, translate_y_str
573 );
574 if(rotate_to_fit)
575 stream_size += fprintf(pdf->fd,
576 "0 1 -1 0 0 0 cm\n" // rotate
577 );
578 stream_size += fprintf(pdf->fd,
579 "%s 0 0 %s 0 0 cm\n" // scale
580 "/Im%d Do\n"
581 "Q\n",
582 scale_x_str, scale_y_str, images[i]->name_id
583 );
584 }
585
586 // DEBUG: draw the bounding box
587 if(images[i]->show_bb)
588 {
589 char bb_x_str[G_ASCII_DTOSTR_BUF_SIZE];
590 char bb_y_str[G_ASCII_DTOSTR_BUF_SIZE];
591 char bb_w_str[G_ASCII_DTOSTR_BUF_SIZE];
592 char bb_h_str[G_ASCII_DTOSTR_BUF_SIZE];
593
594 g_ascii_dtostr(bb_x_str, G_ASCII_DTOSTR_BUF_SIZE, images[i]->bb_x);
595 g_ascii_dtostr(bb_y_str, G_ASCII_DTOSTR_BUF_SIZE, images[i]->bb_y);
596 g_ascii_dtostr(bb_w_str, G_ASCII_DTOSTR_BUF_SIZE, images[i]->bb_width);
597 g_ascii_dtostr(bb_h_str, G_ASCII_DTOSTR_BUF_SIZE, images[i]->bb_height);
598
599 stream_size += fprintf(pdf->fd,
600 "q\n"
601 "%s %s %s %s re\n"
602 "S\n"
603 "Q\n",
604 bb_x_str, bb_y_str, bb_w_str, bb_h_str
605 );
606 }
607 }
608
609 bytes_written += fprintf(pdf->fd,
610 "endstream\n"
611 "endobj\n"
612 );
613 bytes_written += stream_size;
614
615 // length of the last stream
616 _pdf_set_offset(pdf, length_id, pdf->bytes_written + bytes_written);
617 bytes_written += fprintf(pdf->fd, "%d 0 obj\n"
618 "%zu\n"
619 "endobj\n",
620 length_id, stream_size);
621
622 pdf_page->size = bytes_written;
623 pdf->bytes_written += bytes_written;
624
625 return pdf_page;
626 }
627
628 // our writing order is a little strange since we write object 2 (the pages dictionary) at the end of the file
629 // because we don't know the number of pages / objects in advance (due to lazy coding)
dt_pdf_finish(dt_pdf_t * pdf,dt_pdf_page_t ** pages,int n_pages)630 void dt_pdf_finish(dt_pdf_t *pdf, dt_pdf_page_t **pages, int n_pages)
631 {
632 int info_id = pdf->next_id++;
633 size_t bytes_written = 0;
634
635 // the pages dictionary
636 _pdf_set_offset(pdf, 2, pdf->bytes_written + bytes_written);
637 bytes_written += fprintf(pdf->fd,
638 "2 0 obj\n" // yes, this is hardcoded to be object 2, even if written in the end
639 "<<\n"
640 "/Type /Pages\n"
641 "/Kids [\n"
642 );
643 for(int i = 0; i < n_pages; i++)
644 bytes_written += fprintf(pdf->fd, "%d 0 R\n", pages[i]->object_id);
645 bytes_written += fprintf(pdf->fd,
646 "]\n"
647 "/Count %d\n"
648 ">>\n"
649 "endobj\n",
650 n_pages
651 );
652
653 // the info
654
655 // the method to get the time_str is taken from pdftex
656 char time_str[30];
657 time_t t;
658 struct tm lt, gmt;
659 size_t size;
660 int off, off_hours, off_mins;
661
662 /* get the time */
663 t = time(NULL);
664 localtime_r(&t, <);
665 size = strftime(time_str, sizeof(time_str), "D:%Y%m%d%H%M%S", <);
666 /* expected format: "YYYYmmddHHMMSS" */
667 if(size == 0)
668 {
669 /* unexpected, contents of time_str is undefined */
670 time_str[0] = '\0';
671 goto time_error;
672 }
673
674 /* correction for seconds: %S can be in range 00..61,
675 * the PDF reference expects 00..59,
676 * therefore we map "60" and "61" to "59" */
677 if(time_str[14] == '6')
678 {
679 time_str[14] = '5';
680 time_str[15] = '9';
681 time_str[16] = '\0'; /* for safety */
682 }
683
684 /* get the time zone offset */
685 gmtime_r(&t, &gmt);
686
687 /* this calculation method was found in exim's tod.c */
688 off = 60 * (lt.tm_hour - gmt.tm_hour) + lt.tm_min - gmt.tm_min;
689 if(lt.tm_year != gmt.tm_year)
690 off += (lt.tm_year > gmt.tm_year) ? 1440 : -1440;
691 else if(lt.tm_yday != gmt.tm_yday)
692 off += (lt.tm_yday > gmt.tm_yday) ? 1440 : -1440;
693
694 if(off == 0)
695 {
696 time_str[size++] = 'Z';
697 time_str[size] = 0;
698 }
699 else
700 {
701 off_hours = off / 60;
702 off_mins = abs(off - off_hours * 60);
703 snprintf(&time_str[size], 9, "%+03d'%02d'", off_hours, off_mins);
704 }
705
706 time_error:
707
708 _pdf_set_offset(pdf, info_id, pdf->bytes_written + bytes_written);
709 bytes_written += fprintf(pdf->fd,
710 "%d 0 obj\n"
711 "<<\n"
712 "/Title (%s)\n",
713 info_id, pdf->title ? pdf->title : "untitled"
714 );
715 if(*time_str)
716 {
717 bytes_written += fprintf(pdf->fd,
718 "/CreationDate (%s)\n"
719 "/ModDate (%s)\n",
720 time_str, time_str
721 );
722 }
723 bytes_written += fprintf(pdf->fd, "/Producer (%s https://www.darktable.org)\n"
724 ">>\n"
725 "endobj\n",
726 PACKAGE_STRING);
727
728 pdf->bytes_written += bytes_written;
729
730 // the cross reference table
731 fprintf(pdf->fd,
732 "xref\n"
733 "0 %d\n"
734 "0000000000 65535 f \n",
735 pdf->next_id
736 );
737 for(int i = 0; i < pdf->next_id - 1; i++) fprintf(pdf->fd, "%010zu 00000 n \n", pdf->offsets[i]);
738
739 // the trailer
740 fprintf(pdf->fd,
741 "trailer\n"
742 "<<\n"
743 "/Size %d\n"
744 "/Info %d 0 R\n" // we want to have the Info last in the file, so this is /Size - 1
745 "/Root 1 0 R\n"
746 "/ID [<dead> <babe>]\n" // TODO find something less necrophilic, maybe hash of image + history? or just of filename + date :)
747 ">>\n",
748 pdf->next_id, info_id
749 );
750
751 // and finally the file footer with the offset of the xref section
752 fprintf(pdf->fd, "startxref\n"
753 "%zu\n"
754 "%%%%EOF\n",
755 pdf->bytes_written);
756
757 fclose(pdf->fd);
758 free(pdf->offsets);
759 free(pdf);
760 }
761
762 #ifdef STANDALONE
763
764 // just for debugging to read a ppm file
read_ppm(const char * filename,int * wd,int * ht)765 float * read_ppm(const char * filename, int * wd, int * ht)
766 {
767 FILE *f = g_fopen(filename, "rb");
768
769 if(!f)
770 {
771 fprintf(stderr, "can't open input file\n");
772 return NULL;
773 }
774
775 char magic[3];
776 int width, height, max;
777 fscanf(f, "%c%c %d %d %d ", &magic[0], &magic[1], &width, &height, &max);
778 if(magic[0] != 'P' || magic[1] != '6')
779 {
780 fprintf(stderr, "wrong input file format\n");
781 fclose(f);
782 return NULL;
783 }
784
785 float *image = (float*)malloc(sizeof(float) * width * height * 3);
786
787 if(max <= 255)
788 {
789 // read a 8 bit PPM
790 uint8_t *tmp = (uint8_t *)malloc(sizeof(uint8_t) * width * height * 3);
791 int res = fread(tmp, sizeof(uint8_t) * 3, width * height, f);
792 if(res != width * height)
793 {
794 fprintf(stderr, "error reading 8 bit PPM\n");
795 free(tmp);
796 free(image);
797 fclose(f);
798 return NULL;
799 }
800 // and transform it into 0..1 range
801 #ifdef _OPENMP
802 #pragma omp parallel for schedule(static) default(none) shared(image, tmp, width, height, max)
803 #endif
804 for(int i = 0; i < width * height * 3; i++)
805 image[i] = (float)tmp[i] / max;
806 free(tmp);
807 }
808 else
809 {
810 // read a 16 bit PPM
811 uint16_t *tmp = (uint16_t *)malloc(sizeof(uint16_t) * width * height * 3);
812 int res = fread(tmp, sizeof(uint16_t) * 3, width * height, f);
813 if(res != width * height)
814 {
815 fprintf(stderr, "error reading 16 bit PPM\n");
816 free(tmp);
817 free(image);
818 fclose(f);
819 return NULL;
820 }
821 // swap byte order
822 #ifdef _OPENMP
823 #pragma omp parallel for schedule(static) default(none) shared(tmp, width, height)
824 #endif
825 for(int k = 0; k < 3 * width * height; k++)
826 tmp[k] = ((tmp[k] & 0xff) << 8) | (tmp[k] >> 8);
827 // and transform it into 0..1 range
828 #ifdef _OPENMP
829 #pragma omp parallel for schedule(static) default(none) shared(image, tmp, max, width, height)
830 #endif
831 for(int i = 0; i < width * height * 3; i++)
832 image[i] = (float)tmp[i] / max;
833 free(tmp);
834 }
835 fclose(f);
836
837 if(wd) *wd = width;
838 if(ht) *ht = height;
839 return image;
840 }
841
main(int argc,char * argv[])842 int main(int argc, char *argv[])
843 {
844 if(argc < 3)
845 {
846 fprintf(stderr, "usage: %s <input PPM> [<input PPM> ...] <output PDF>\n", argv[0]);
847 exit(1);
848 }
849
850 // example for A4 portrait, which is 210 mm x 297 mm.
851 float page_width, page_height, border;
852 dt_pdf_parse_length("10 mm", &border); // add an empty space of 1 cm for the sake of demonstration
853 dt_pdf_parse_paper_size("a4", &page_width, &page_height);
854
855 // since this is just stupid example code we are going to put the images into the pdf twice:
856 // I am not 100% sure if image objects may be reused like that in PDFs, but it seems to work
857
858 dt_pdf_t *pdf = dt_pdf_start(argv[argc - 1], page_width, page_height, 360, DT_PDF_STREAM_ENCODER_FLATE);
859
860 // we can load icc profiles and assign them to images. For testing something like
861 // https://github.com/boxerab/graphicsmagick/raw/master/profiles/BRG.icc works really good
862 int icc_id = dt_pdf_add_icc(pdf, "BRG.icc");
863
864 const int n_images = argc - 2;
865 const int n_pages = argc - 1;
866
867 dt_pdf_image_t *images[n_images];
868 dt_pdf_page_t *pages[n_pages]; // one extra page for the stupid image dump
869
870 // load all the images. it doesn't matter when we do it, as long as they are loaded before
871 // creating the page they should appear on (and even that is just a constraint of this code)
872 for(int i = 0; i < n_images; i++)
873 {
874 int width, height;
875 float *image = read_ppm(argv[i + 1], &width, &height);
876 if(!image) exit(1);
877 uint16_t *data = (uint16_t *)malloc(sizeof(uint16_t) * 3 * width * height);
878 if(!data)
879 {
880 free(image);
881 exit(1);
882 }
883
884 #ifdef _OPENMP
885 #pragma omp parallel for schedule(static) default(none) shared(image, data, width, height)
886 #endif
887 for(int i = 0; i < width * height * 3; i++)
888 data[i] = CLIP(image[i]) * 65535;
889
890 images[i] = dt_pdf_add_image(pdf, (unsigned char *)data, width, height, 16, icc_id, border);
891 free(image);
892 free(data);
893 }
894
895 // add pages with one image each, filling the page minus borders
896 for(int i = 0; i < n_images; i++)
897 pages[i] = dt_pdf_add_page(pdf, &images[i], 1);
898
899 // add the whole bunch of images to the last page
900 // images' default bounding boxen span the whole page, so set them a little smaller first, also enable bounding box drawing.
901 // we can also set outline mode afterwards. note that it is NOT safe to load images with outline_mode = 1 and then set it to 0 later!
902 {
903 // TODO: use border and add new pages when we filled one up
904 float bb_size = dt_pdf_mm_to_point(60);
905 int n_x = page_width / bb_size;
906 float bb_empty = (page_width - (n_x * bb_size)) / n_x;
907 float bb_step = bb_empty + bb_size;
908
909 float x = bb_empty * 0.5, y = bb_empty * 0.5;
910
911 for(int i = 0; i < n_images; i++)
912 {
913 images[i]->outline_mode = TRUE;
914 images[i]->show_bb = TRUE;
915 images[i]->bb_width = bb_size;
916 images[i]->bb_height = bb_size;
917 images[i]->bb_x = x;
918 images[i]->bb_y = y;
919 x += bb_step;
920 if((i+1) % n_x == 0)
921 {
922 x = bb_empty * 0.5;
923 y += bb_step;
924 }
925 }
926 }
927
928 pages[n_images] = dt_pdf_add_page(pdf, images, n_images);
929
930 dt_pdf_finish(pdf, pages, n_pages);
931
932 for(int i = 0; i < n_images; i++)
933 free(images[i]);
934 for(int i = 0; i < n_pages; i++)
935 free(pages[i]);
936
937 return 0;
938 }
939
940 #endif // STANDALONE
941
942 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
943 // vim: shiftwidth=2 expandtab tabstop=2 cindent
944 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
945