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