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, &lt);
665   size = strftime(time_str, sizeof(time_str), "D:%Y%m%d%H%M%S", &lt);
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