File: buildpdf.cc

package info (click to toggle)
auto-multiple-choice 1.4.0-2~exp1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 18,520 kB
  • sloc: perl: 23,839; xml: 20,920; cpp: 1,791; makefile: 472; ansic: 186; sh: 80
file content (1249 lines) | stat: -rw-r--r-- 35,173 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
/*

 Copyright (C) 2013-2017 Alexis Bienvenue <paamc@passoire.fr>

 This file is part of Auto-Multiple-Choice

 Auto-Multiple-Choice is free software: you can redistribute it
 and/or modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation, either version 2 of
 the License, or (at your option) any later version.

 Auto-Multiple-Choice is distributed in the hope that it will be
 useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with Auto-Multiple-Choice.  If not, see
 <http://www.gnu.org/licenses/>.

*/

#ifndef __BUILDPDF__

#define __BUILDPDF__ 1

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <cairo.h>
#include <cairo-pdf.h>
#include <poppler.h>
#include <pango/pangocairo.h>

#include <string>

#ifdef DEBUG
#include <fstream>
#include <iostream>
#endif

#include "opencv2/core/core.hpp"

#if CV_MAJOR_VERSION > 2
  #define OPENCV_23 1
  #define OPENCV_21 1
  #define OPENCV_20 1
  #define OPENCV_30 1
#else
  #if CV_MAJOR_VERSION == 2
    #define OPENCV_20 1
    #if CV_MINOR_VERSION >= 1
       #define OPENCV_21 1
    #endif
    #if CV_MINOR_VERSION >= 3
       #define OPENCV_23 1
    #endif
  #endif
#endif

#include "opencv2/imgproc/imgproc.hpp"
#ifdef OPENCV_30
  #include "opencv2/imgcodecs/imgcodecs.hpp"
#else
  #include "opencv2/highgui/highgui.hpp"
#endif

#define FORMAT_JPEG 1
#define FORMAT_PNG 2

/*

  Helpers to read PNG files from memory (as an array or as a vector)
  with Cairo

 */

struct buffer_closure {
  uchar *buffer;
  unsigned long int length;
  unsigned long int offset;
};

#define BUFFER_CLOSURE(ptr) ((buffer_closure*) (ptr))

static cairo_status_t read_buffer(void *closure, uchar *data, unsigned int length) {
  if(BUFFER_CLOSURE(closure)->offset + length > BUFFER_CLOSURE(closure)->length) {
    return(CAIRO_STATUS_READ_ERROR);
  }
  memcpy(data,BUFFER_CLOSURE(closure)->buffer + BUFFER_CLOSURE(closure)->offset,length);
  BUFFER_CLOSURE(closure)->offset += length;
  return(CAIRO_STATUS_SUCCESS);
}

struct vector_closure {
  std::vector<uchar>::iterator iterator;
  unsigned long int length;
};

#define VECTOR_CLOSURE(ptr) ((vector_closure*) (ptr))

static cairo_status_t read_vector(void *closure, uchar *data, unsigned int length) {
  if(VECTOR_CLOSURE(closure)->length < length) {
    return(CAIRO_STATUS_READ_ERROR);
  }
  std::copy(VECTOR_CLOSURE(closure)->iterator, VECTOR_CLOSURE(closure)->iterator+length,
	    (uchar*)data);
  VECTOR_CLOSURE(closure)->iterator += length;
  VECTOR_CLOSURE(closure)->length -= length;
  return(CAIRO_STATUS_SUCCESS);
}

/*

  BuildPdf

  This class is used by AMC-annotate to build a pdf from image files
  (scans), PDF files (subject pages), with transformation matrix
  support and drawing support (rectangles, circles, marks, text).

*/

/* COORDINATE SYSTEMS:
   
   All coordinate systems has top-left (0,0).

   - LAYOUT/SUBJECT coordinates are pixel-based coordinates of an
     image with density given with dpi, that corresponds to a question
     page. So (0,0) is the top-left corner of the page, and the
     bottom-right corner of the page is (,).

   - SCAN coordinates are pixel-based coordinates on the scan image.

   - PDF coordinates are point(=1/72 inch)-based coordinates of the
     PDF page.
   
   Note that when drawing, all coordinates will be given to BuildPdf
   in pixels (with (0,0) = top-left corner), usually in LAYOUT
   coordinates, but possibly also in PDF coordinates. These
   coordinates will be mapped to PDF coordinates using a
   transformation matrix.
 */

class BuildPdf {
public:
  /* constructor: w and h are the page size in pixels (in the layout
     coordinate system), and d the "dots per point", that is the
     number of pixels in one pt (1pt is 1/72 inches).

  */

  BuildPdf(double w, double h, double d):
    width_in_pixels(w), height_in_pixels(h), dppt(d), n_pages(-1),
    user_one_point(0), margin(0),
    document(NULL), layout(NULL), surface(NULL), cr(NULL),
    image_cr(NULL), image_surface(NULL), fake_image_buffer(NULL),
    font_description(NULL),
    line_width(1.0), font("Linux Libertine O 12"), debug(0),
    scan_expansion(1.0), scan_resize_factor(1.0),
    embedded_image_format(FORMAT_JPEG),
    image_buffer(), scan_max_width(0), scan_max_height(0),
    png_compression_level(9), jpeg_quality(75) { 
    printf(": w_pix=%g h_pix=%g dppt=%g\n",
	   width_in_pixels, height_in_pixels, dppt);
  };

  ~BuildPdf();

  /* call set_debug(1) to get more debugging output on stderr. */
  void set_debug(int d) { debug = d; }


  /* **************************************************************** */
  /* METHODS TO INSERT SCANS (AS BACKGROUND)                          */
  /* **************************************************************** */

  /* the set_* functions can be used to set some parameters' values:

     - embedded_image_format is the format scans will be converted to
       before including them to the PDF file. There are currently two
       choices: FORMAT_PNG or FORMAT_JPEG.

     - jpeg_quality is the JPEG quality (between 0 and 100) used in
       FORMAT_JPEG mode. A small value will lead to small PDF size,
       but with small image quality...

     - png_compression is the PNG compression level (from 1 to 9). Use
       default value 9 for small PDF size (with images longer to
       encode/decode).

     - scan_max_height and scan_max_width define a maximum size for
       the scans: scans which are larger will be resized so that their
       dimensions don't exceed these values. This can allow to get
       smaller PDF files.

     - margin is the margin size in points, used for the global
       verdict text and the questions scores.

  */

  void set_embedded_image_format(int format) { embedded_image_format = format; }
  void set_embedded_png() { embedded_image_format = FORMAT_PNG; }
  void set_embedded_jpeg() { embedded_image_format = FORMAT_JPEG; }
  void set_jpeg_quality(int quality) { jpeg_quality = quality; }
  void set_png_compression_level(int l) { png_compression_level = l; }
  void set_scan_max_height(int mh) { scan_max_height = mh; }
  void set_scan_max_width(int mw) { scan_max_width = mw; }
  void set_margin(double m) { margin = m; }

  /* start_output starts to build a PDF into file
     output_filename. Call it once for each PDF to create, before
     addind images or drawing on it  */

  int start_output(char* output_filename);

  /* close_output finishes with building the PDF, and closes the
     file. */

  void close_output();

  /* next_page begins with another blank page. */

  int next_page();

  /* new_page_from_png begins with another page, with the PNG image as
     a background. The PNG image can be given with its filename, as a
     memoty buffer (pointer and lenght), or as a std::vector memory
     buffer.

     next_page() is called first, unless skip_show_page is set.
  */

  int new_page_from_png(const char* filename);
  int new_page_from_png(void *buffer, unsigned long int buffer_length);
  int new_page_from_png(std::vector<uchar> &buf, int skip_show_page = 0);

  /* resize_scan resizes the cv::Mat image so that its dimensions does
     not exceed scan_max_height and scan_max_width. The scaling factor
     is saved for later use (we need to know it to get the right
     scaling factor between PDF coordinates and scan coordinates) */

  void resize_scan(cv::Mat &image);

  /* new_page_from_image begins with another page, with the image as a
     background.

     The first version will first converted to PNG or JPEG (depending
     on embedded_image_format) using OpenCV.

     With the two latter versions, the image is given as a memory
     buffer, and is attached to the PDF file using the specified
     mime_type (which can be either image/png or image/jpeg).

  */

  int new_page_from_image(const char* filename);
  int new_page_from_image(std::vector<uchar> &image_data, const char* mime_type,
			  int width, int height);
  int new_page_from_image(unsigned char *data, unsigned int size,
			  const char* mime_type,
			  int width, int height);

  /* load_pdf loads a PDF file, to be used later by
     new_page_from_pdf. */

  int load_pdf(char* filename);

  /* new_page_from_pdf begins with another page, using page page_nb of
     the loaded PDF as a background. */

  int new_page_from_pdf(int page_nb);

  /* **************************************************************** */
  /* METHODS TO DRAW ON THE PAGE                                      */
  /* **************************************************************** */

  /* set_line_width sets the line width (in points) for next
     drawings. */

  void set_line_width(double lw);

  /* set_font sets the font for next drawings.
     It then calls validate_font, which updates the font
     description with this new font. */

  int set_font(const char* font);
  int validate_font();
  
  /* color sets the color for next drawings, either with RBG or
     RGBA. Color values must be between 0.0 and 1.0. */

  void color(double r,double g, double b, double a) {
    cairo_set_source_rgba(cr, r, g, b, a);
  }
  void color(double r,double g, double b) {
    cairo_set_source_rgb(cr, r, g, b);
  }

  /* set_matrix_to_scan sets the matrix that transforms layout (subject)
     coordinates to scan coordinates (as recorded in the layout AMC
     database). */

  void set_matrix_to_scan(double a, double b, double c ,double d, double e, double f);

  /* identity_matrix sets the matrix to identity. It can be used when
     the background is the question page, not a scan, or when the
     following drawings will be done with subject/layout coordinates.
  */

  void identity_matrix();

  /* keep_on_scan moves the (x,y) point (in layout/subject
     coordinates) so that the corresponding point stays on the scan */

  void keep_on_scan(double *x, double *y);

  /* drawing symbols (the rectangle on which the symbol should be
     based is given): */

  void draw_rectangle(double xmin, double xmax, double ymin, double ymax);
  void fill_rectangle(double xmin, double xmax, double ymin, double ymax);
  void draw_mark(double xmin, double xmax, double ymin, double ymax);
  void draw_circle(double xmin, double xmax, double ymin, double ymax);

  /* draw_text draws the UTF8 string at (x,y), with x-anchor and
     y-anchor given by xpos and ypos. When xpos=0.0, the text is
     written at the right of (x,y). When xpos=1.0, the text is written
     at the left of (x,y). When 0.5 for exemple, the text is
     x-centered at (x,y). The same applies for ypos in the
     y-direction. */

  void draw_text(double x, double y,
		 double xpos, double ypos, const char *text);

  /* draw_text_margin writes a text in the margin (left margin if
     xside=0, and right margin if xside=1). The point used is a point
     at the border of the margin, so that xpos=0.0 should be used for
     left margin, and xpos=1.0 for tight margin.
  */

  void draw_text_margin(int xside, double y,
			double xpos, double ypos, const char *text);

  /* draw_text_rectangle writes a text in the given rectangle (the
     text is scaled down if necessary to fit in the rectangle).
  */

  int draw_text_rectangle(double xmin, double xmax,
			  double ymin, double ymax,
			  const char *text);

private:
  // dimensions of one subject page in the layout coordinate system
  double width_in_pixels;
  double height_in_pixels;
  // dots per pt for the layout
  double dppt;
  // number of pages created so far for current PDF file. Equals -1 if
  // no PDF file is opened for output
  int n_pages;
  // PDF document loaded (usualy the subject), from which one can copy
  // pages to the output PDF
  PopplerDocument *document;
  // Pango layout used to write texts
  PangoLayout *layout;
  // Cairo environment used to draw on the page
  cairo_surface_t *surface;
  cairo_t *cr;
  cairo_matrix_t matrix;
  // Cairo environment used to draw the background image (scan)
  cairo_t *image_cr;
  cairo_surface_t *image_surface;
  // Fake PNG image used to attach images to the PDF file. It will
  // never contain any particular image, only random stuff, but is
  // needed to attach images properly to the PDF output.
  unsigned char *fake_image_buffer;
  // Image currently been attached to the PDF output
  std::vector<uchar> image_buffer;
  // use this dimension in Cairo user coordinate system to get one
  // point (1/72 inch) in the PDF coordinate system
  double user_one_point;
  // scaling factor used to expand or shrink the scan to make it the
  // same size as the PDF output.
  double scan_expansion;
  // scaling factor used when resizing the scan (when its dimensions
  // exceed the scan_max_* values)
  double scan_resize_factor;

  // drawing parameters
  double margin;
  double line_width;
  std::string font;
  // debuging?
  int debug;
  // image parameters
  int embedded_image_format;
  int png_compression_level;
  int jpeg_quality;
  int scan_max_height;
  int scan_max_width;

  void set_matrix(double a, double b, double c ,double d, double e, double f);
  void set_matrix(cairo_matrix_t *m);

  double normalize_distance();
  double normalize_matrix_distance(cairo_matrix_t *m);
  PangoLayout* r_font_size_layout(double ratio);
  PangoFontDescription *font_description;
  int new_page_from_image_surface(cairo_surface_t *is);
  void draw_text(PangoLayout* local_layout,
		 double x, double y, double xpos, double ypos, const char *text);
  void free_buffer();
};

BuildPdf::~BuildPdf() {
  close_output();
  if(document != NULL) g_object_unref(document);
}

int BuildPdf::start_output(char* output_filename) {

  // close current PDF document, if one

  close_output();

  printf(": opening -> %s\n", output_filename);
  if(debug) {
    printf("; Create main surface\n");
  }

  // create a new PDF Cairo surface, with dimensions in points

  surface = cairo_pdf_surface_create(output_filename,
				     width_in_pixels / dppt,
				     height_in_pixels / dppt);

  cairo_status_t status = cairo_surface_status(surface);
  if(status != CAIRO_STATUS_SUCCESS) {
    printf("! ERROR : creating surface - %s\n",
	   cairo_status_to_string(status));
    cairo_surface_destroy(surface);
    surface = NULL;
    return(1);
  }

  // Create Cairo context for drawings and texts (will not be used for
  // images)

  if(debug) {
    printf("; Create cr\n");
  }
  cr = cairo_create(surface);

  if(cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
    printf("! ERROR : creating cairo - %s\n",
	   cairo_status_to_string(cairo_status(cr)));
    cairo_surface_destroy(surface);
    cairo_destroy(cr);
    surface = NULL;
    cr = NULL;
    return(1);
  }

  // Create Pango Cairo layout for texts
  
  if(debug) {
    printf("; Create layout\n");
  }
  layout = pango_cairo_create_layout(cr);
  if(layout == NULL) {
     printf("! ERROR : creating pango/cairo layout - %s\n",
	   cairo_status_to_string(status));
    cairo_surface_destroy(surface);
    cairo_destroy(cr);
    surface = NULL;
    cr = NULL;
    return(1);
  }

  // Updates the font description with the right font and uses it
  // for the new layout

  if(validate_font()) {
    return(2);
  }

  // initialization. user_one_point will be set when using set_matrix

  n_pages = 0;
  user_one_point = 0;

  if(debug) {  
    printf(": OK\n");
  }
  return(0);
}

void BuildPdf::close_output() {
  if(n_pages >= 0) {
    // free all allocated objects...

    printf(": closing...\n");
    next_page();
    cairo_surface_finish(surface);
    cairo_surface_destroy(surface);
    surface = NULL;
    cairo_destroy(cr);
    cr = NULL;
    if(image_cr != NULL) cairo_destroy(image_cr);
    image_cr = NULL;
    if(layout != NULL) {
      g_object_unref(layout);
      layout = NULL;
    }
    n_pages = -1;
  }
}

int BuildPdf::next_page() {
  if(n_pages<0) {
    printf("! ERROR: next_page in closed document\n");
    return(1);
  }
  if(n_pages >= 1) {

    // Adds current page to PDF output

    cairo_show_page(cr);
    if(debug) {
      printf("; Show page\n");
    }

    // Destroy objects used to insert the background scan

    if(image_cr != NULL) {
      if(debug) {
	printf("; Destroy image_cr\n");
      }
      cairo_destroy(image_cr);
      image_cr = NULL;
    }
    if(image_surface != NULL) {
      if(debug) {
	printf("; Destroy image_surface\n");
      }
      cairo_surface_finish(image_surface);
      cairo_surface_destroy(image_surface);
      image_surface = NULL;
    }
    if(fake_image_buffer != NULL) {
      free_buffer();
    }
    scan_resize_factor = 1.0;
    scan_expansion = 1.0;
  }

  n_pages++;
  return(0);
}

int BuildPdf::new_page_from_png(const char* filename) {
  if(next_page()) return(1);

  if(debug) {
    printf(": PNG < %s\n", filename);
    printf("; Create image_surface from PNG\n");
  }
  cairo_surface_t *is = cairo_image_surface_create_from_png(filename);
  return(new_page_from_image_surface(is));
}

int BuildPdf::new_page_from_png(void *buffer, unsigned long int buffer_length) {
  if(next_page()) return(1);

  buffer_closure closure;
  closure.buffer = (uchar*) buffer;
  closure.length = buffer_length;
  closure.offset = 0;

  if(debug) {
    printf(": PNG < BUFFER\n");
    printf("; Create image_surface from PNG stream\n");
  }
  cairo_surface_t *is = cairo_image_surface_create_from_png_stream(read_buffer, &closure);
  return(new_page_from_image_surface(is));
}

int BuildPdf::new_page_from_png(std::vector<uchar> &buf, int skip_show_page) {
  if(!skip_show_page) {
    if(next_page()) return(1);
  }

  vector_closure closure;
  closure.iterator = buf.begin();
  closure.length = buf.size();

  if(debug) {
    printf(": PNG < BUFFER\n");
    printf("; Create image_surface from PNG stream\n");
  }
  cairo_surface_t *is = cairo_image_surface_create_from_png_stream(read_vector, &closure);
  return(new_page_from_image_surface(is));
}

// Free buffer used for the fake image

void BuildPdf::free_buffer() {
  if(debug) {
    printf("; Free fake_image_buffer\n");
  }
  free(fake_image_buffer);
  fake_image_buffer = NULL;
}

void detach(void* args) {
  if(*((int*) args)) {
    printf("; DETACH\n");
  }
}

#define ZFORMAT CAIRO_FORMAT_A1
int BuildPdf::new_page_from_image(unsigned char *data, unsigned int size,
				  const char* mime_type,
				  int width, int height) {
  if(data == NULL) {
    printf("! ERROR : new_page_from_image from null data\n");
    return(1);
  }
  if(fake_image_buffer != NULL) {
    printf("! ERROR : fake_image_buffer already present\n");
    return(1);
  }
#ifdef DEBUG
  std::ofstream outfile("/tmp/opencv-exported", 
			std::ios::out | std::ios::binary);
  outfile.write((const char*) data, size);
#endif

  // Creates a fake image surface (to get minimal memory size, we use
  // CAIRO_FORMAT_A1 format) with associated memory buffer.

  int stride = cairo_format_stride_for_width(ZFORMAT, width);
  if(debug) {
    printf("; Create fake_image_buffer\n");
  }
  fake_image_buffer = (unsigned char*) malloc(stride * height);
  if(debug) {
    printf("; Create image_surface for DATA\n");
  }
  cairo_surface_t *is =
    cairo_image_surface_create_for_data(fake_image_buffer,
					ZFORMAT,
					width, height,
					stride);
  if(debug) {
    printf("; Attach mime %s to image_surface\n", mime_type);
  }

#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
  if(!cairo_surface_supports_mime_type(surface, mime_type)) {
    printf("! ERROR: surface does not handle %s\n",
	   mime_type);
    return(1);
  }
#endif

  // Attach the real image to the surface

  cairo_status_t status =
    cairo_surface_set_mime_data(is, mime_type,
				data, size,
				detach, (void*) (&debug));
  if(status != CAIRO_STATUS_SUCCESS) {
    printf("! ERROR : setting mime data - %s\n",
	   cairo_status_to_string(status));
    cairo_surface_destroy(is);
    free_buffer();
    return(-2);
  }

  // Uses the surface to create a new page
  
  int status_np = new_page_from_image_surface(is);
  return(status_np);
}

int BuildPdf::new_page_from_image(std::vector<uchar> &image_data,
				  const char* mime_type,
				  int width, int height) {
  return(new_page_from_image(image_data.data(), image_data.size(),
			     mime_type, width, height));
}

void BuildPdf::resize_scan(cv::Mat &image) {
  cv::Size s = image.size();

  // compute the resize factor to be used to get dimensions no more
  // than scan_max_*

  double fx = 2;
  double fy = 2;
  if(scan_max_width > 0) {
    fx = (double) scan_max_width / s.width;
  }
  if(scan_max_height > 0) {
    fy = (double) scan_max_height / s.height;
  }
  if(debug) {
    printf(": fx=%g fy=%g.\n", fx, fy);
  }
  if(fx < fy) {
    scan_resize_factor = fx;
  } else {
    scan_resize_factor = fy;
  }

  // resize the image if needed

  if(scan_resize_factor < 1.0) {
    cv::resize(image, image, cv::Size(),
	       scan_resize_factor, scan_resize_factor, cv::INTER_AREA);
  } else {
    scan_resize_factor = 1.0;
    if(debug) {
      printf(": No need to resize.\n");
    }
  }
}

int BuildPdf::new_page_from_image(const char* filename) {
  if(next_page()) return(1);

  int direct_png = 0;

  if(debug) {  
    printf(": IMAGE < %s\n", filename);
  }

  // read the image from disk to memory

  cv::Mat image = cv::imread(filename);
  const char* mime_type;

  if(debug) {  
    printf(": type=%d depth=%d channels=%d\n",
	   image.type(), image.depth(), image.channels());
  }

  // resize it if needed

  resize_scan(image);

  // encode the image to a PNG or JPEG image buffer

  if(embedded_image_format == FORMAT_JPEG) {
    std::vector<int> params;
    params.push_back(cv::IMWRITE_JPEG_QUALITY);
    params.push_back(jpeg_quality);
    imencode(".jpg", image, image_buffer, params);
    mime_type = CAIRO_MIME_TYPE_JPEG;
  } else if(embedded_image_format == FORMAT_PNG) {
    std::vector<int> params;
    params.push_back(cv::IMWRITE_PNG_COMPRESSION);
    params.push_back(png_compression_level);
    imencode(".png", image, image_buffer, params);
    mime_type = CAIRO_MIME_TYPE_PNG;
    direct_png = 1;
  } else {
    printf("! ERROR: invalid embedded_image_format - %d\n",
	   embedded_image_format);
    return(3);
  }
  
  cv::Size s = image.size();
  if(debug) {
    printf(": converted to %s [Q=%d C=%d] (%.1f KB) w=%d h=%d\n",
	   mime_type,
	   jpeg_quality, png_compression_level,
	   (double) image_buffer.size() / 1024,
	   s.width, s.height);
  }

  int r;
  if(direct_png) {
    // PNG images can't be "attached"
    // (cairo_surface_supports_mime_type would return FALSE), so we
    // directly insert them into the PDF output
    r = new_page_from_png(image_buffer, 1);
  } else {
    // JPEG images are attached to the image surface, and then
    // inserted to the PDF output
    r = new_page_from_image(image_buffer, mime_type, s.width, s.height);
  }

  if(debug) {
    printf("; Image buffer exit\n");
  }
  
  return(r);
}

int BuildPdf::new_page_from_image_surface(cairo_surface_t *is) {
  if(debug) {
    printf("; Entering new_page_from_image_surface\n");
  }
  if(image_surface != NULL) {
    printf("! ERROR : image_surface already in use\n");
    return(1);
  } else {
    if(is == NULL) {
      printf("! ERROR : NULL image_surface\n");
      return(1);
    }
    image_surface = is;
  }
  
  cairo_status_t image_surface_status = cairo_surface_status(image_surface);
  if(image_surface_status != CAIRO_STATUS_SUCCESS) {
    printf("! ERROR : creating image surface / %s\n",
	   cairo_status_to_string(image_surface_status));
    cairo_surface_destroy(image_surface);
    return(1);
  }
  int w = cairo_image_surface_get_width(image_surface);
  int h = cairo_image_surface_get_height(image_surface);
  if(w <= 0 || h <= 0) {
    printf("! ERROR : image dimensions should be positive (%dx%d)\n",
	   w, h);
    cairo_surface_destroy(image_surface);
    return(1);
  }
  
  // rx and ry are the scaling factors that has to be used to set the
  // image surface with the same dimensions as the PDF output

  double rx = width_in_pixels / dppt / w;
  double ry = height_in_pixels / dppt / h;

  if(rx < ry) {
    scan_expansion = rx;
  } else {
    scan_expansion = ry;
  }

  if(debug) {
    printf(": R=%g (%g,%g)\n", scan_expansion, rx, ry);
    printf("; Create and scale image_cr\n");
  }

  // Create the Cairo surface that will contain the image, with a
  // matrix that will scale the image to the PDF output dimensions

  image_cr = cairo_create(surface);

  cairo_identity_matrix(image_cr);
  cairo_scale(image_cr, scan_expansion, scan_expansion);

  if(debug) {
    printf("; set_source_surface\n");
  }

  // paint the image using context image_cr

  cairo_set_source_surface(image_cr, image_surface, 0, 0);
  if(debug) {
    printf("; paint from image_cr\n");
  }
  cairo_paint(image_cr);

  if(debug) {
    printf("; Exit new_page_from_image_surface: OK\n");
  }
  return(0);
}

int BuildPdf::load_pdf(char* filename) {
  GError *error = NULL;
  gchar *uri;

  if(document != NULL) g_object_unref(document);

  uri = g_filename_to_uri(filename, NULL, &error);
  if(uri == NULL) {
    printf("! ERROR: poppler fail: %s\n", error->message);
    return 1;
  }

  // loads the PDF document using Poppler

  document = poppler_document_new_from_file(uri, NULL, &error);
  if(document == NULL) {
    printf("! ERROR: poppler fail: %s\n", error->message);
    return 1;
  }

  identity_matrix();
  return(0);
}

int BuildPdf::new_page_from_pdf(int page_nb) {
  if(next_page()) {
    return(1);
  }

  if(document == NULL) {
    printf("! ERROR: no pdf loaded.\n");
    return(1);
  }

  // Inserts one page from pre-loaded PDF document, using
  // Poppler/Cairo

  PopplerPage *page = poppler_document_get_page(document, page_nb-1);
  if(page == NULL) {
    printf("! ERROR:poppler fail: page not found.\n");
    return 1;
  }
  cairo_identity_matrix(cr);
  poppler_page_render_for_printing(page, cr);
  g_object_unref(page);

  identity_matrix();
  return(0);
}

/* normalize_distance returns a user distance that will be mapped to a
   one point (=1/72 inch) distance on the PDF output, using current
   transformation matrix. */

double BuildPdf::normalize_distance() {
  double dx, dy;
  dx = 1.0;
  dy = 1.0;
  cairo_device_to_user_distance(cr, &dx, &dy);
  return( sqrt((dx * dx + dy * dy) / 2.0) );
}

/* the same, with any matrix */

double BuildPdf::normalize_matrix_distance(cairo_matrix_t *m) {
  double dx, dy;
  dx = 1.0;
  dy = 1.0;
  cairo_matrix_transform_distance(m, &dx, &dy);
  return( sqrt((dx * dx + dy * dy) / 2.0) );
}

void BuildPdf::set_line_width(double lw) {
  if(lw >= 0) {
    line_width = lw;
    if(line_width * user_one_point > 0) 
      cairo_set_line_width(cr, line_width * user_one_point);
  }
}

int BuildPdf::set_font(const char* f) {
  font = "";
  font.append(f);
  return(validate_font());
}

int BuildPdf::validate_font() {
  font_description = pango_font_description_from_string(font.c_str());
  if(font_description == NULL) {
    printf("! ERROR : font description creation\n");
    return(1);
  }
  pango_layout_set_font_description(layout, font_description);
  return(0);
}

/* r_font_size_layout creates a new Pango layout with a font size the
   is scaled with ratio.*/

PangoLayout* BuildPdf::r_font_size_layout(double ratio) {
  PangoLayout *local_layout = pango_layout_copy(layout);
  if(local_layout == NULL) {
    printf("! ERROR : creating local pango layout.\n");
    return(NULL);
  }
  const PangoFontDescription *desc = pango_layout_get_font_description(layout);
  if(desc == NULL) {
    printf("! ERROR : creating pango font description.\n");
    g_object_unref(local_layout);
    return(NULL);
  }
  gint size = pango_font_description_get_size(desc);
  PangoFontDescription *new_desc = pango_font_description_copy(desc);
  if(new_desc == NULL) {
    printf("! ERROR : creating local pango font description.\n");
    g_object_unref(local_layout);
    return(NULL);
  }
  if(pango_font_description_get_size_is_absolute(desc)) {
    pango_font_description_set_absolute_size(new_desc, size * ratio);
  } else {
    pango_font_description_set_size(new_desc, size * ratio);
  }
  pango_layout_set_font_description(local_layout, new_desc);
  return(local_layout);
}

/* cairo_matrix_scale_after makes the transformation matrix m to be
   composed with scaling with factor r, so that the resulting
   transformation is the same as 1) using matrix m 2) scaling. It
   seems from documentation that cairo_matrix_scale does it, but this
   is not: the resulting transformation with cairo_matrix_scale is the
   same as 1) scaling 2) using m.
*/

void cairo_matrix_scale_after(cairo_matrix_t *m, double r) {
  m->xx *= r;
  m->xy *= r;
  m->yx *= r;
  m->yy *= r;
  m->x0 *= r;
  m->y0 *= r;
}

/* same, but for a context matrix */

void cairo_scale_after(cairo_t *cr, double r) {
  cairo_matrix_t m;
  cairo_get_matrix(cr, &m);
  cairo_matrix_scale_after(&m, r);
  cairo_set_matrix(cr, &m);
}

/* set_matrix_to_scan gives the matrix that transforms layout
   coordinates to scan coordinates. This matrix is composed with a
   scaling so that layout coordinates are mapped to resized and
   expanded scan coordinates. Then, the matrix is used for drawings 
*/

void BuildPdf::set_matrix_to_scan(double a, double b, double c ,double d, double e, double f) {
  if(debug) {
    printf("; Set matrix to scan coordinates\n;   resize_factor=%g expansion=%g\n",
	   scan_resize_factor, scan_expansion);
  }

  cairo_matrix_init(&matrix, a, c, b, d, e, f);
  cairo_matrix_scale_after(&matrix, dppt * scan_resize_factor * scan_expansion);
  set_matrix(&matrix);
}

/* These two test_* functions are used for debugging */

void test_point(cairo_matrix_t *m, double x, double y) {
  double tx = x;
  double ty = y;
  printf(";   (%g,%g) ->", tx, ty);
  cairo_matrix_transform_point(m, &tx, &ty);
  printf(" (%g,%g)\n", tx, ty);
}

void test_matrix(cairo_matrix_t *m, double xmax, double ymax) {
  printf(";   x'=%7.3f x + %7.3f y + %6.3f\n", m->xx, m->xy, m->x0);
  printf(";   y'=%7.3f x + %7.3f y + %6.3f\n", m->yx, m->yy, m->y0);
  test_point(m, 0, 0);
  test_point(m, xmax, 0);
  test_point(m, 0, ymax);
  test_point(m, xmax, ymax);
}

void BuildPdf::set_matrix(double a, double b, double c ,double d, double e, double f) {
  if(debug) {
    printf("; Set matrix\n");
  }

  cairo_matrix_init(&matrix, a, c, b, d, e, f);
  set_matrix(&matrix);
}

/* set_matrix sets up transformations that will be used when
   drawing. The matrix m maps layout coordinates to pixel-based PDF
   coordinates. So it is scaled to get pt-based PDF coordinates before
   beeing used.
*/

void BuildPdf::set_matrix(cairo_matrix_t *m) {
  cairo_set_matrix(cr, m);
  cairo_scale_after(cr, 1 / dppt);

  // updates user_one_point, and the line width

  user_one_point = normalize_distance();
  set_line_width(line_width);

  // debugging...

  if(debug) {
    cairo_matrix_t ctm;
    cairo_get_matrix(cr, &ctm);
    double tx, ty;
    printf("; subject to scan matrix:\n");
    printf(";   dppt=%g\n", dppt);
    test_matrix(m, width_in_pixels, height_in_pixels);
    printf("; cr matrix:\n");
    printf(";   user 1pt=%g\n", user_one_point);
    test_matrix(&ctm, width_in_pixels, height_in_pixels);
  }
#ifdef DEBUG
  color(0.0, 0.5, 0.2, 0.5);
  draw_rectangle(0, width_in_pixels, 0, height_in_pixels);
#endif

  // updates Pango layout with new scaling factors
  pango_cairo_context_set_resolution(pango_layout_get_context(layout),
				     user_one_point * 72.);
  pango_cairo_update_layout(cr, layout);
  validate_font();
}

void BuildPdf::identity_matrix() {
  set_matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
}

#define FIXX(xp) ((xp) - matrix.xy * *y - matrix.x0) / matrix.xx
void BuildPdf::keep_on_scan(double *x, double *y) {
  double xp = *x;
  double yp = *y;
  double margin_in_pixels = margin * dppt * 72.;
  cairo_matrix_transform_point(&matrix, &xp, &yp);
  if(xp < margin_in_pixels) {
    *x = FIXX(margin_in_pixels);
  } else if(xp > width_in_pixels - margin_in_pixels) {
    *x = FIXX(width_in_pixels - margin_in_pixels);
  }
}

void BuildPdf::draw_rectangle(double xmin, double xmax, double ymin, double ymax) {
  if(debug) {
    printf("; draw rectangle\n");
  }
  cairo_rectangle(cr, xmin, ymin, xmax - xmin, ymax - ymin);
  cairo_stroke(cr);
}

void BuildPdf::fill_rectangle(double xmin, double xmax, double ymin, double ymax) {
  if(debug) {
    printf("; fill rectangle\n");
  }
  cairo_rectangle(cr, xmin, ymin, xmax - xmin, ymax - ymin);
  cairo_fill(cr);
}

void BuildPdf::draw_mark(double xmin, double xmax, double ymin, double ymax) {
  if(debug) {
    printf("; draw mark\n");
  }
  cairo_move_to(cr, xmin, ymin);
  cairo_line_to(cr, xmax, ymax);
  cairo_move_to(cr, xmin, ymax);
  cairo_line_to(cr, xmax, ymin);
  cairo_stroke(cr);
}

void BuildPdf::draw_text(PangoLayout* local_layout,
			 double x, double y,
			 double xpos, double ypos,
			 const char *text) {
  if(debug) {
    printf("; draw text\n");
  }
  PangoRectangle extents;

  if(x<0) x += width_in_pixels;
  if(y<0) y += height_in_pixels;

  pango_layout_set_text(local_layout, text, -1);
  pango_layout_get_pixel_extents(local_layout, &extents, NULL);
  if(debug) {
    printf("TEXT=\"%s\" X=%d Y=%d W=%d H=%d\n",
	   text,
	   extents.x, extents.y,
	   extents.width, extents.height);
  }
  cairo_move_to(cr,
		x - xpos * extents.width - extents.x,
		y - ypos * extents.height - extents.y);
  pango_cairo_show_layout(cr, local_layout);
}

void BuildPdf::draw_text(double x, double y,
			 double xpos, double ypos,
			 const char *text) {
  draw_text(layout, x, y, xpos, ypos, text);
}

void BuildPdf::draw_text_margin(int xside, double y,
				double xpos, double ypos,
				const char *text) {
  double x;
  if(xside == 1) {
    x = width_in_pixels;
  } else {
    x = 0;
  }
  keep_on_scan(&x, &y);
  draw_text(x, y, xpos, ypos, text);
}

int BuildPdf::draw_text_rectangle(double xmin, double xmax,
				   double ymin, double ymax,
				   const char *text) {
  double r, rp;
  PangoRectangle extents;
  PangoLayout* local_layout;

  pango_layout_set_text(layout, text, -1);
  pango_layout_get_pixel_extents(layout, &extents, NULL);
  if(debug) {
    printf("TEXT=\"%s\" X=%d Y=%d W=%d H=%d\n",
	   text,
	   extents.x, extents.y,
	   extents.width, extents.height);
  }
  r = (xmax - xmin) / extents.width;
  rp = (ymax - ymin) / extents.height;
  if(rp < r) r = rp;
  if(debug) {
    printf(": ratio=%g\n", r);
  }

  local_layout = r_font_size_layout(r);
  if(local_layout == NULL) {
    printf("! ERROR: r_font_size_layout failed.");
    return(1);
  }
  draw_text(local_layout,
	    (xmin + xmax) / 2, (ymin + ymax) / 2,
	    0.5, 0.5, text);

  g_object_unref(local_layout);
  return(0);
}

void BuildPdf::draw_circle(double xmin, double xmax, double ymin, double ymax) {
  if(debug) {
    printf("; draw circle\n");
  }
  cairo_new_path(cr);
  cairo_arc(cr, (xmin + xmax) / 2, (ymin + ymax) / 2,
	    sqrt((xmax - xmin) * (xmax - xmin) + (ymax - ymin) * (ymax - ymin)) / 2,
	    0.0, 2 * M_PI);
  cairo_stroke(cr);
}

#endif