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