]> mj.ucw.cz Git - paperjam.git/blob - pdf-tools.cc
c170d8ae9a3b0fc2dfbddadfb249ca59d9c77ab0
[paperjam.git] / pdf-tools.cc
1 /*
2  *      Auxiliary functions for processing PDF files
3  *
4  *      (c) 2018 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <cstdarg>
8 #include <cstdio>
9 #include <cstdlib>
10
11 #include <iconv.h>
12
13 using namespace std;
14
15 #include "pdf-tools.h"
16
17 #include <qpdf/QUtil.hh>
18 #include <qpdf/Pl_Concatenate.hh>
19
20 /*** Messages ***/
21
22 int debug_mode;
23 int debug_indent;
24
25 void debug(const char *msg, ...)
26 {
27         if (!debug_mode)
28                 return;
29         va_list args;
30         va_start(args, msg);
31         fprintf(stderr, "%*s", debug_indent, "");
32         vfprintf(stderr, msg, args);
33         fputc('\n', stderr);
34         va_end(args);
35 }
36
37 void warn(const char *msg, ...)
38 {
39         va_list args;
40         va_start(args, msg);
41         fprintf(stderr, "WARNING: ");
42         vfprintf(stderr, msg, args);
43         fputc('\n', stderr);
44         va_end(args);
45 }
46
47 void die(const char *msg, ...)
48 {
49         va_list args;
50         va_start(args, msg);
51         fprintf(stderr, "ERROR: ");
52         vfprintf(stderr, msg, args);
53         fputc('\n', stderr);
54         va_end(args);
55         exit(1);
56 }
57
58 void bad(const char *msg, ...)
59 {
60         va_list args;
61         va_start(args, msg);
62         char buf[1024];
63         vsnprintf(buf, sizeof(buf), msg, args);
64         va_end(args);
65
66         printf("error: %s\n", buf);
67         die("BAD: %s", buf);
68 }
69
70 /*** Transformation matrices ***/
71
72 // Construct string representation of a transformation matrix
73 string pdf_matrix::to_string() {
74         string s;
75         for (int i=0; i<6; i++) {
76                 if (i)
77                         s += " ";
78                 char buf[16];
79                 snprintf(buf, sizeof(buf), "%.3f", m[i]);
80                 s += buf;
81         }
82         return s;
83 }
84
85 /*** Bounding boxes ***/
86
87 QPDFObjectHandle BBox::to_array()
88 {
89         QPDFObjectHandle a = QPDFObjectHandle::newArray();
90         a.appendItem(QPDFObjectHandle::newReal(x_min, 1));
91         a.appendItem(QPDFObjectHandle::newReal(y_min, 1));
92         a.appendItem(QPDFObjectHandle::newReal(x_max, 1));
93         a.appendItem(QPDFObjectHandle::newReal(y_max, 1));
94         return a;
95 }
96
97 string BBox::to_rect()
98 {
99         char buf[64];
100         snprintf(buf, sizeof(buf), "%.1f %.1f %.1f %.1f", x_min, y_min, width(), height());
101         return string(buf);
102 }
103
104 bool BBox::parse(QPDFObjectHandle h)
105 {
106         if (!h.isArray() || h.getArrayNItems() != 4)
107                 return false;
108         double x[4];
109         for (int i=0; i<4; i++) {
110                 QPDFObjectHandle item = h.getArrayItem(i);
111                 if (!item.isNumber())
112                         return false;
113                 x[i] = item.getNumericValue();
114         }
115         x_min = x[0];
116         y_min = x[1];
117         x_max = x[2];
118         y_max = x[3];
119         return true;
120 }
121
122 void BBox::transform(pdf_matrix &m)
123 {
124         m.apply(&x_min, &y_min);
125         m.apply(&x_max, &y_max);
126         if (x_max < x_min)
127                 swap(x_min, x_max);
128         if (y_max < y_min)
129                 swap(y_min, y_max);
130 }
131
132 /*** Unicode strings ***/
133
134 // Construct PDF representation of a UTF-8 string
135 QPDFObjectHandle unicode_string(string s)
136 {
137         // If it is ASCII only, use the string directly
138         bool ascii_only = true;
139         for (char c: s)
140                 if (c < 0x20 || c > 0x7e)
141                         ascii_only = false;
142         if (ascii_only)
143                 return QPDFObjectHandle::newString(s);
144
145         // Use iconv to convert the string to big-endian UTF-16
146         iconv_t conv = iconv_open("UTF-16BE", "UTF-8");
147         if (conv == (iconv_t) -1)
148                 die("Cannot initialize iconv: %m");
149
150         char *in_ptr = (char *) s.c_str();      // Work around bad API of iconv()
151         size_t in_len = strlen(in_ptr);
152         size_t out_len = 2*in_len + 2;          // Worst case (including the BOM)
153         char out_buf[out_len];
154         char *out_ptr = out_buf;
155         size_t res = iconv(conv, &in_ptr, &in_len, &out_ptr, &out_len);
156         if (res == (size_t) -1)
157                 die("iconv failed: %m");
158         if (in_len)
159                 die("iconv stopped before the end of input");
160
161         iconv_close(conv);
162
163         // Package UTF-16 in a PDF string
164         string out;
165         out += 0xfe;
166         out += 0xff;
167         for (char *p = out_buf; p < out_ptr; p++)
168                 out += *p;
169         return QPDFObjectHandle::newString(out);
170 }
171
172 /*** Conversion of pages to XObjects ***/
173
174 static BBox get_trim_box(QPDFObjectHandle page)
175 {
176         static const char * const boxes[] = { "/TrimBox", "/CropBox", "/MediaBox", NULL };
177         for (int i=0; boxes[i]; i++)
178                 if (page.hasKey(boxes[i]))
179                         return BBox(page.getKey(boxes[i]));
180         warn("Page has no trimbox, falling back to A4");
181         return BBox(0, 0, A4_WIDTH, A4_HEIGHT);
182 }
183
184 /* Conversion of pages to XObjects is inspired by CUPS's pdftopdf filter. */
185 class CombineFromContents_Provider : public QPDFObjectHandle::StreamDataProvider {
186         private:
187                 vector<QPDFObjectHandle> contents;
188         public:
189                 CombineFromContents_Provider(const vector<QPDFObjectHandle> &contents) : contents(contents) { }
190                 void provideStreamData(int objid UNUSED, int generation UNUSED, Pipeline* pipeline) {
191                         Pl_Concatenate concat("concat", pipeline);
192                         for (int i=0; i < (int)contents.size(); i++)
193                                 contents[i].pipeStreamData(&concat, true, false, false);
194                         concat.manualFinish();
195                 }
196 };
197
198 QPDFObjectHandle page_to_xobject(QPDF *out, QPDFObjectHandle page)
199 {
200         page.assertPageObject();
201
202         QPDFObjectHandle xo_stream = QPDFObjectHandle::newStream(out);
203         QPDFObjectHandle xo_dict = xo_stream.getDict();
204
205         xo_dict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject"));
206         xo_dict.replaceKey("/Subtype", QPDFObjectHandle::newName("/Form"));
207         xo_dict.replaceKey("/FormType", QPDFObjectHandle::newInteger(1));
208
209         BBox box = get_trim_box(page);
210         xo_dict.replaceKey("/BBox", box.to_array());
211
212         xo_dict.replaceKey("/Resources", page.getKey("/Resources"));
213         if (page.hasKey("/Group"))
214                 xo_dict.replaceKey("/Group", page.getKey("/Group"));
215
216         if (page.hasKey("/UserUnit")) {
217                 double u = page.getKey("/UserUnit").getNumericValue();
218                 QPDFObjectHandle m = QPDFObjectHandle::newArray();
219                 m.appendItem(QPDFObjectHandle::newReal(u, 3));
220                 m.appendItem(QPDFObjectHandle::newReal(0, 0));
221                 m.appendItem(QPDFObjectHandle::newReal(0, 0));
222                 m.appendItem(QPDFObjectHandle::newReal(u, 3));
223                 m.appendItem(QPDFObjectHandle::newReal(0, 0));
224                 m.appendItem(QPDFObjectHandle::newReal(0, 0));
225                 xo_dict.replaceKey("/Matrix", m);
226         }
227
228         vector<QPDFObjectHandle> contents = page.getPageContents();
229         auto ph = PointerHolder<QPDFObjectHandle::StreamDataProvider>(new CombineFromContents_Provider(contents));
230         xo_stream.replaceStreamData(ph, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
231         return xo_stream;
232 }