Commit c2023db265ea35ad7d0ab0cd989f16479bcb798d

Authored by Jay Berkenbilt
1 parent c1e53f14

Implement changes suggested by Zarko and our subsequent conversations:

- Add a way to set the minimum PDF version
 - Add a way to force the PDF version
 - Have isEncrypted return true if an /Encrypt dictionary exists even
   when we can't read the file
 - Allow qpdf_init_write to be called multiple times
 - Update some comments in headers


git-svn-id: svn+q:///qpdf/trunk@748 71b93d88-0707-0410-a8cf-f5a4172ac649
ChangeLog
1 2009-10-04 Jay Berkenbilt <ejb@ql.org> 1 2009-10-04 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Add methods to QPDFWriter and corresponding command line
  4 + arguments to qpdf to set the minimum output PDF version and also
  5 + to force the version to a particular value.
  6 +
3 * libqpdf/QPDF.cc (processXRefStream): warn and ignore extra xref 7 * libqpdf/QPDF.cc (processXRefStream): warn and ignore extra xref
4 stream entries when stream is larger than reported size. This 8 stream entries when stream is larger than reported size. This
5 used to be a fatal error. (Fixes qpdf-Bugs-2872265.) 9 used to be a fatal error. (Fixes qpdf-Bugs-2872265.)
1 -Now  
2 -===  
3 -  
4 - * Add functions to set minimum version and to force pdf version  
5 -  
6 - * Make multiple calls to init_write safe; document that write  
7 - parameter settings must be repeated  
8 -  
9 - * qpdf_is_encrypted returns false for encrypted file when incorrect  
10 - password is given  
11 -  
12 -  
13 2.1 1 2.1
14 === 2 ===
15 3
include/qpdf/QPDFWriter.hh
@@ -34,7 +34,13 @@ class Pl_Count; @@ -34,7 +34,13 @@ class Pl_Count;
34 class QPDFWriter 34 class QPDFWriter
35 { 35 {
36 public: 36 public:
37 - // Passing null as filename means write to stdout 37 + // Passing null as filename means write to stdout. QPDFWriter
  38 + // will create a zero-length output file upon construction. If
  39 + // write fails, the empty or partially written file will not be
  40 + // deleted. This is by design: sometimes the partial file may be
  41 + // useful for tracking down problems. If your application doesn't
  42 + // want the partially written file to be left behind, you should
  43 + // delete it the eventual call to write fails.
38 DLL_EXPORT 44 DLL_EXPORT
39 QPDFWriter(QPDF& pdf, char const* filename); 45 QPDFWriter(QPDF& pdf, char const* filename);
40 DLL_EXPORT 46 DLL_EXPORT
@@ -78,6 +84,30 @@ class QPDFWriter @@ -78,6 +84,30 @@ class QPDFWriter
78 DLL_EXPORT 84 DLL_EXPORT
79 void setQDFMode(bool); 85 void setQDFMode(bool);
80 86
  87 + // Set the minimum PDF version. If the PDF version of the input
  88 + // file (or previously set minimum version) is less than the
  89 + // version passed to this method, the PDF version of the output
  90 + // file will be set to this value. If the original PDF file's
  91 + // version or previously set minimum version is already this
  92 + // version or later, the original file's version will be used.
  93 + // QPDFWriter automatically sets the minimum version to 1.4 when
  94 + // R3 encryption parameters are used, and to 1.5 when object
  95 + // streams are used.
  96 + DLL_EXPORT
  97 + void setMinimumPDFVersion(std::string const&);
  98 +
  99 + // Force the PDF version of the output file to be a given version.
  100 + // Use of this function may create PDF files that will not work
  101 + // properly with older PDF viewers. When a PDF version is set
  102 + // using this function, qpdf will use this version even if the
  103 + // file contains features that are not supported in that version
  104 + // of PDF. In other words, you should only use this function if
  105 + // you are sure the PDF file in question has no features of newer
  106 + // versions of PDF or if you are willing to create files that old
  107 + // viewers may try to open but not be able to properly interpret.
  108 + DLL_EXPORT
  109 + void forcePDFVersion(std::string const&);
  110 +
81 // Cause a static /ID value to be generated. Use only in test 111 // Cause a static /ID value to be generated. Use only in test
82 // suites. 112 // suites.
83 DLL_EXPORT 113 DLL_EXPORT
@@ -241,6 +271,7 @@ class QPDFWriter @@ -241,6 +271,7 @@ class QPDFWriter
241 std::string id1; // for /ID key of 271 std::string id1; // for /ID key of
242 std::string id2; // trailer dictionary 272 std::string id2; // trailer dictionary
243 std::string min_pdf_version; 273 std::string min_pdf_version;
  274 + std::string forced_pdf_version;
244 int encryption_dict_objid; 275 int encryption_dict_objid;
245 std::string cur_data_key; 276 std::string cur_data_key;
246 std::list<PointerHolder<Pipeline> > to_delete; 277 std::list<PointerHolder<Pipeline> > to_delete;
include/qpdf/qpdf-c.h
@@ -189,7 +189,12 @@ extern &quot;C&quot; { @@ -189,7 +189,12 @@ extern &quot;C&quot; {
189 /* Supply the name of the file to be written and initialize the 189 /* Supply the name of the file to be written and initialize the
190 * qpdf_data object to handle writing operations. This function 190 * qpdf_data object to handle writing operations. This function
191 * also attempts to create the file. The PDF data is not written 191 * also attempts to create the file. The PDF data is not written
192 - * until the call to qpdf_write. 192 + * until the call to qpdf_write. qpdf_init_write may be called
  193 + * multiple times for the same qpdf_data object. When
  194 + * qpdf_init_write is called, all information from previous calls
  195 + * to functions that set write parameters (qpdf_set_linearization,
  196 + * etc.) is lost, so any write parameter functions must be called
  197 + * again.
193 */ 198 */
194 DLL_EXPORT 199 DLL_EXPORT
195 QPDF_ERROR_CODE qpdf_init_write(qpdf_data qpdf, char const* filename); 200 QPDF_ERROR_CODE qpdf_init_write(qpdf_data qpdf, char const* filename);
@@ -256,6 +261,12 @@ extern &quot;C&quot; { @@ -256,6 +261,12 @@ extern &quot;C&quot; {
256 DLL_EXPORT 261 DLL_EXPORT
257 void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value); 262 void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value);
258 263
  264 + DLL_EXPORT
  265 + void qpdf_set_minimum_pdf_version(qpdf_data qpdf, char const* version);
  266 +
  267 + DLL_EXPORT
  268 + void qpdf_force_pdf_version(qpdf_data qpdf, char const* version);
  269 +
259 /* Do actual write operation. */ 270 /* Do actual write operation. */
260 DLL_EXPORT 271 DLL_EXPORT
261 QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf); 272 QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf);
libqpdf/QPDFWriter.cc
@@ -100,6 +100,37 @@ QPDFWriter::setQDFMode(bool val) @@ -100,6 +100,37 @@ QPDFWriter::setQDFMode(bool val)
100 } 100 }
101 101
102 void 102 void
  103 +QPDFWriter::setMinimumPDFVersion(std::string const& version)
  104 +{
  105 + bool set_version = false;
  106 + if (this->min_pdf_version.empty())
  107 + {
  108 + set_version = true;
  109 + }
  110 + else
  111 + {
  112 + float v = atof(version.c_str());
  113 + float mv = atof(this->min_pdf_version.c_str());
  114 + if (v > mv)
  115 + {
  116 + QTC::TC("qpdf", "QPDFWriter increasing minimum version");
  117 + set_version = true;
  118 + }
  119 + }
  120 +
  121 + if (set_version)
  122 + {
  123 + this->min_pdf_version = version;
  124 + }
  125 +}
  126 +
  127 +void
  128 +QPDFWriter::forcePDFVersion(std::string const& version)
  129 +{
  130 + this->forced_pdf_version = version;
  131 +}
  132 +
  133 +void
103 QPDFWriter::setStaticID(bool val) 134 QPDFWriter::setStaticID(bool val)
104 { 135 {
105 this->static_id = val; 136 this->static_id = val;
@@ -147,7 +178,7 @@ QPDFWriter::setR2EncryptionParameters( @@ -147,7 +178,7 @@ QPDFWriter::setR2EncryptionParameters(
147 clear.insert(6); 178 clear.insert(6);
148 } 179 }
149 180
150 - this->min_pdf_version = "1.3"; 181 + setMinimumPDFVersion("1.3");
151 setEncryptionParameters(user_password, owner_password, 1, 2, 5, clear); 182 setEncryptionParameters(user_password, owner_password, 1, 2, 5, clear);
152 } 183 }
153 184
@@ -221,7 +252,7 @@ QPDFWriter::setR3EncryptionParameters( @@ -221,7 +252,7 @@ QPDFWriter::setR3EncryptionParameters(
221 // no default so gcc warns for missing cases 252 // no default so gcc warns for missing cases
222 } 253 }
223 254
224 - this->min_pdf_version = "1.4"; 255 + setMinimumPDFVersion("1.4");
225 setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear); 256 setEncryptionParameters(user_password, owner_password, 2, 3, 16, clear);
226 } 257 }
227 258
@@ -1361,7 +1392,7 @@ QPDFWriter::write() @@ -1361,7 +1392,7 @@ QPDFWriter::write()
1361 1392
1362 if (! this->object_stream_to_objects.empty()) 1393 if (! this->object_stream_to_objects.empty())
1363 { 1394 {
1364 - this->min_pdf_version = "1.5"; 1395 + setMinimumPDFVersion("1.5");
1365 } 1396 }
1366 1397
1367 generateID(); 1398 generateID();
@@ -1417,15 +1448,12 @@ QPDFWriter::writeEncryptionDictionary() @@ -1417,15 +1448,12 @@ QPDFWriter::writeEncryptionDictionary()
1417 void 1448 void
1418 QPDFWriter::writeHeader() 1449 QPDFWriter::writeHeader()
1419 { 1450 {
1420 - std::string version = pdf.getPDFVersion();  
1421 - if (! this->min_pdf_version.empty()) 1451 + setMinimumPDFVersion(pdf.getPDFVersion());
  1452 + std::string version = this->min_pdf_version;
  1453 + if (! this->forced_pdf_version.empty())
1422 { 1454 {
1423 - float ov = atof(version.c_str());  
1424 - float mv = atof(this->min_pdf_version.c_str());  
1425 - if (mv > ov)  
1426 - {  
1427 - version = this->min_pdf_version;  
1428 - } 1455 + QTC::TC("qpdf", "QPDFWriter using forced PDF version");
  1456 + version = this->forced_pdf_version;
1429 } 1457 }
1430 1458
1431 writeString("%PDF-"); 1459 writeString("%PDF-");
libqpdf/QPDF_encryption.cc
@@ -289,6 +289,11 @@ QPDF::initializeEncryption() @@ -289,6 +289,11 @@ QPDF::initializeEncryption()
289 return; 289 return;
290 } 290 }
291 291
  292 + // Go ahead and set this->encryption here. That way, isEncrypted
  293 + // will return true even if there were errors reading the
  294 + // encryption dictionary.
  295 + this->encrypted = true;
  296 +
292 QPDFObjectHandle id_obj = this->trailer.getKey("/ID"); 297 QPDFObjectHandle id_obj = this->trailer.getKey("/ID");
293 if (! (id_obj.isArray() && 298 if (! (id_obj.isArray() &&
294 (id_obj.getArrayNItems() == 2) && 299 (id_obj.getArrayNItems() == 2) &&
@@ -377,7 +382,6 @@ QPDF::initializeEncryption() @@ -377,7 +382,6 @@ QPDF::initializeEncryption()
377 throw QPDFExc(this->file.getName() + ": invalid password"); 382 throw QPDFExc(this->file.getName() + ": invalid password");
378 } 383 }
379 384
380 - this->encrypted = true;  
381 this->encryption_key = compute_encryption_key(this->user_password, data); 385 this->encryption_key = compute_encryption_key(this->user_password, data);
382 } 386 }
383 387
libqpdf/qpdf-c.cc
@@ -252,6 +252,12 @@ DLL_EXPORT @@ -252,6 +252,12 @@ DLL_EXPORT
252 QPDF_ERROR_CODE qpdf_init_write(qpdf_data qpdf, char const* filename) 252 QPDF_ERROR_CODE qpdf_init_write(qpdf_data qpdf, char const* filename)
253 { 253 {
254 QPDF_ERROR_CODE status = QPDF_SUCCESS; 254 QPDF_ERROR_CODE status = QPDF_SUCCESS;
  255 + if (qpdf->qpdf_writer)
  256 + {
  257 + QTC::TC("qpdf", "qpdf-c called qpdf_init_write multiple times");
  258 + delete qpdf->qpdf_writer;
  259 + qpdf->qpdf_writer = 0;
  260 + }
255 try 261 try
256 { 262 {
257 qpdf->qpdf_writer = new QPDFWriter(*(qpdf->qpdf), filename); 263 qpdf->qpdf_writer = new QPDFWriter(*(qpdf->qpdf), filename);
@@ -391,6 +397,20 @@ void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value) @@ -391,6 +397,20 @@ void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value)
391 } 397 }
392 398
393 DLL_EXPORT 399 DLL_EXPORT
  400 +void qpdf_set_minimum_pdf_version(qpdf_data qpdf, char const* version)
  401 +{
  402 + QTC::TC("qpdf", "qpdf-c called qpdf_set_minimum_pdf_version");
  403 + qpdf->qpdf_writer->setMinimumPDFVersion(version);
  404 +}
  405 +
  406 +DLL_EXPORT
  407 +void qpdf_force_pdf_version(qpdf_data qpdf, char const* version)
  408 +{
  409 + QTC::TC("qpdf", "qpdf-c called qpdf_force_pdf_version");
  410 + qpdf->qpdf_writer->forcePDFVersion(version);
  411 +}
  412 +
  413 +DLL_EXPORT
394 QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf) 414 QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf)
395 { 415 {
396 QPDF_ERROR_CODE status = QPDF_SUCCESS; 416 QPDF_ERROR_CODE status = QPDF_SUCCESS;
qpdf/qpdf-ctest.c
@@ -20,7 +20,8 @@ static void report_errors() @@ -20,7 +20,8 @@ static void report_errors()
20 20
21 static void test01(char const* infile, 21 static void test01(char const* infile,
22 char const* password, 22 char const* password,
23 - char const* outfile) 23 + char const* outfile,
  24 + char const* outfile2)
24 { 25 {
25 qpdf_read(qpdf, infile, password); 26 qpdf_read(qpdf, infile, password);
26 printf("version: %s\n", qpdf_get_pdf_version(qpdf)); 27 printf("version: %s\n", qpdf_get_pdf_version(qpdf));
@@ -53,7 +54,8 @@ static void test01(char const* infile, @@ -53,7 +54,8 @@ static void test01(char const* infile,
53 54
54 static void test02(char const* infile, 55 static void test02(char const* infile,
55 char const* password, 56 char const* password,
56 - char const* outfile) 57 + char const* outfile,
  58 + char const* outfile2)
57 { 59 {
58 qpdf_set_suppress_warnings(qpdf, QPDF_TRUE); 60 qpdf_set_suppress_warnings(qpdf, QPDF_TRUE);
59 if (((qpdf_read(qpdf, infile, password) & QPDF_ERRORS) == 0) && 61 if (((qpdf_read(qpdf, infile, password) & QPDF_ERRORS) == 0) &&
@@ -67,7 +69,8 @@ static void test02(char const* infile, @@ -67,7 +69,8 @@ static void test02(char const* infile,
67 69
68 static void test03(char const* infile, 70 static void test03(char const* infile,
69 char const* password, 71 char const* password,
70 - char const* outfile) 72 + char const* outfile,
  73 + char const* outfile2)
71 { 74 {
72 qpdf_read(qpdf, infile, password); 75 qpdf_read(qpdf, infile, password);
73 qpdf_init_write(qpdf, outfile); 76 qpdf_init_write(qpdf, outfile);
@@ -79,7 +82,8 @@ static void test03(char const* infile, @@ -79,7 +82,8 @@ static void test03(char const* infile,
79 82
80 static void test04(char const* infile, 83 static void test04(char const* infile,
81 char const* password, 84 char const* password,
82 - char const* outfile) 85 + char const* outfile,
  86 + char const* outfile2)
83 { 87 {
84 qpdf_set_ignore_xref_streams(qpdf, QPDF_TRUE); 88 qpdf_set_ignore_xref_streams(qpdf, QPDF_TRUE);
85 qpdf_read(qpdf, infile, password); 89 qpdf_read(qpdf, infile, password);
@@ -91,7 +95,8 @@ static void test04(char const* infile, @@ -91,7 +95,8 @@ static void test04(char const* infile,
91 95
92 static void test05(char const* infile, 96 static void test05(char const* infile,
93 char const* password, 97 char const* password,
94 - char const* outfile) 98 + char const* outfile,
  99 + char const* outfile2)
95 { 100 {
96 qpdf_read(qpdf, infile, password); 101 qpdf_read(qpdf, infile, password);
97 qpdf_init_write(qpdf, outfile); 102 qpdf_init_write(qpdf, outfile);
@@ -103,7 +108,8 @@ static void test05(char const* infile, @@ -103,7 +108,8 @@ static void test05(char const* infile,
103 108
104 static void test06(char const* infile, 109 static void test06(char const* infile,
105 char const* password, 110 char const* password,
106 - char const* outfile) 111 + char const* outfile,
  112 + char const* outfile2)
107 { 113 {
108 qpdf_read(qpdf, infile, password); 114 qpdf_read(qpdf, infile, password);
109 qpdf_init_write(qpdf, outfile); 115 qpdf_init_write(qpdf, outfile);
@@ -115,7 +121,8 @@ static void test06(char const* infile, @@ -115,7 +121,8 @@ static void test06(char const* infile,
115 121
116 static void test07(char const* infile, 122 static void test07(char const* infile,
117 char const* password, 123 char const* password,
118 - char const* outfile) 124 + char const* outfile,
  125 + char const* outfile2)
119 { 126 {
120 qpdf_read(qpdf, infile, password); 127 qpdf_read(qpdf, infile, password);
121 qpdf_init_write(qpdf, outfile); 128 qpdf_init_write(qpdf, outfile);
@@ -127,7 +134,8 @@ static void test07(char const* infile, @@ -127,7 +134,8 @@ static void test07(char const* infile,
127 134
128 static void test08(char const* infile, 135 static void test08(char const* infile,
129 char const* password, 136 char const* password,
130 - char const* outfile) 137 + char const* outfile,
  138 + char const* outfile2)
131 { 139 {
132 qpdf_read(qpdf, infile, password); 140 qpdf_read(qpdf, infile, password);
133 qpdf_init_write(qpdf, outfile); 141 qpdf_init_write(qpdf, outfile);
@@ -140,7 +148,8 @@ static void test08(char const* infile, @@ -140,7 +148,8 @@ static void test08(char const* infile,
140 148
141 static void test09(char const* infile, 149 static void test09(char const* infile,
142 char const* password, 150 char const* password,
143 - char const* outfile) 151 + char const* outfile,
  152 + char const* outfile2)
144 { 153 {
145 qpdf_read(qpdf, infile, password); 154 qpdf_read(qpdf, infile, password);
146 qpdf_init_write(qpdf, outfile); 155 qpdf_init_write(qpdf, outfile);
@@ -152,7 +161,8 @@ static void test09(char const* infile, @@ -152,7 +161,8 @@ static void test09(char const* infile,
152 161
153 static void test10(char const* infile, 162 static void test10(char const* infile,
154 char const* password, 163 char const* password,
155 - char const* outfile) 164 + char const* outfile,
  165 + char const* outfile2)
156 { 166 {
157 qpdf_set_attempt_recovery(qpdf, QPDF_FALSE); 167 qpdf_set_attempt_recovery(qpdf, QPDF_FALSE);
158 qpdf_read(qpdf, infile, password); 168 qpdf_read(qpdf, infile, password);
@@ -161,7 +171,8 @@ static void test10(char const* infile, @@ -161,7 +171,8 @@ static void test10(char const* infile,
161 171
162 static void test11(char const* infile, 172 static void test11(char const* infile,
163 char const* password, 173 char const* password,
164 - char const* outfile) 174 + char const* outfile,
  175 + char const* outfile2)
165 { 176 {
166 qpdf_read(qpdf, infile, password); 177 qpdf_read(qpdf, infile, password);
167 qpdf_init_write(qpdf, outfile); 178 qpdf_init_write(qpdf, outfile);
@@ -174,7 +185,8 @@ static void test11(char const* infile, @@ -174,7 +185,8 @@ static void test11(char const* infile,
174 185
175 static void test12(char const* infile, 186 static void test12(char const* infile,
176 char const* password, 187 char const* password,
177 - char const* outfile) 188 + char const* outfile,
  189 + char const* outfile2)
178 { 190 {
179 qpdf_read(qpdf, infile, password); 191 qpdf_read(qpdf, infile, password);
180 qpdf_init_write(qpdf, outfile); 192 qpdf_init_write(qpdf, outfile);
@@ -188,7 +200,8 @@ static void test12(char const* infile, @@ -188,7 +200,8 @@ static void test12(char const* infile,
188 200
189 static void test13(char const* infile, 201 static void test13(char const* infile,
190 char const* password, 202 char const* password,
191 - char const* outfile) 203 + char const* outfile,
  204 + char const* outfile2)
192 { 205 {
193 qpdf_read(qpdf, infile, password); 206 qpdf_read(qpdf, infile, password);
194 printf("user password: %s\n", qpdf_get_user_password(qpdf)); 207 printf("user password: %s\n", qpdf_get_user_password(qpdf));
@@ -199,15 +212,33 @@ static void test13(char const* infile, @@ -199,15 +212,33 @@ static void test13(char const* infile,
199 report_errors(); 212 report_errors();
200 } 213 }
201 214
  215 +static void test14(char const* infile,
  216 + char const* password,
  217 + char const* outfile,
  218 + char const* outfile2)
  219 +{
  220 + qpdf_read(qpdf, infile, password);
  221 + qpdf_init_write(qpdf, outfile);
  222 + qpdf_set_static_ID(qpdf, QPDF_TRUE);
  223 + qpdf_set_minimum_pdf_version(qpdf, "1.6");
  224 + qpdf_write(qpdf);
  225 + qpdf_init_write(qpdf, outfile2);
  226 + qpdf_set_static_ID(qpdf, QPDF_TRUE);
  227 + qpdf_force_pdf_version(qpdf, "1.4");
  228 + qpdf_write(qpdf);
  229 + report_errors();
  230 +}
  231 +
202 int main(int argc, char* argv[]) 232 int main(int argc, char* argv[])
203 { 233 {
204 char* whoami = 0; 234 char* whoami = 0;
205 char* p = 0; 235 char* p = 0;
206 int n = 0; 236 int n = 0;
207 - char const* infile;  
208 - char const* password;  
209 - char const* outfile;  
210 - void (*fn)(char const*, char const*, char const*) = 0; 237 + char const* infile = 0;
  238 + char const* password = 0;
  239 + char const* outfile = 0;
  240 + char const* outfile2 = 0;
  241 + void (*fn)(char const*, char const*, char const*, char const*) = 0;
211 242
212 if ((p = strrchr(argv[0], '/')) != NULL) 243 if ((p = strrchr(argv[0], '/')) != NULL)
213 { 244 {
@@ -221,7 +252,7 @@ int main(int argc, char* argv[]) @@ -221,7 +252,7 @@ int main(int argc, char* argv[])
221 { 252 {
222 whoami = argv[0]; 253 whoami = argv[0];
223 } 254 }
224 - if (argc != 5) 255 + if (argc < 5)
225 { 256 {
226 fprintf(stderr, "usage: %s n infile password outfile\n", whoami); 257 fprintf(stderr, "usage: %s n infile password outfile\n", whoami);
227 exit(2); 258 exit(2);
@@ -231,6 +262,7 @@ int main(int argc, char* argv[]) @@ -231,6 +262,7 @@ int main(int argc, char* argv[])
231 infile = argv[2]; 262 infile = argv[2];
232 password = argv[3]; 263 password = argv[3];
233 outfile = argv[4]; 264 outfile = argv[4];
  265 + outfile2 = (argc > 5 ? argv[5] : 0);
234 266
235 fn = ((n == 1) ? test01 : 267 fn = ((n == 1) ? test01 :
236 (n == 2) ? test02 : 268 (n == 2) ? test02 :
@@ -245,6 +277,7 @@ int main(int argc, char* argv[]) @@ -245,6 +277,7 @@ int main(int argc, char* argv[])
245 (n == 11) ? test11 : 277 (n == 11) ? test11 :
246 (n == 12) ? test12 : 278 (n == 12) ? test12 :
247 (n == 13) ? test13 : 279 (n == 13) ? test13 :
  280 + (n == 14) ? test14 :
248 0); 281 0);
249 282
250 if (fn == 0) 283 if (fn == 0)
@@ -254,7 +287,7 @@ int main(int argc, char* argv[]) @@ -254,7 +287,7 @@ int main(int argc, char* argv[])
254 } 287 }
255 288
256 qpdf = qpdf_init(); 289 qpdf = qpdf_init();
257 - fn(infile, password, outfile); 290 + fn(infile, password, outfile, outfile2);
258 qpdf_cleanup(&qpdf); 291 qpdf_cleanup(&qpdf);
259 assert(qpdf == 0); 292 assert(qpdf == 0);
260 293
qpdf/qpdf.cc
@@ -103,6 +103,8 @@ familiar with the PDF file format or who are PDF developers.\n\ @@ -103,6 +103,8 @@ familiar with the PDF file format or who are PDF developers.\n\
103 --object-streams=mode controls handing of object streams\n\ 103 --object-streams=mode controls handing of object streams\n\
104 --ignore-xref-streams tells qpdf to ignore any cross-reference streams\n\ 104 --ignore-xref-streams tells qpdf to ignore any cross-reference streams\n\
105 --qdf turns on \"QDF mode\" (below)\n\ 105 --qdf turns on \"QDF mode\" (below)\n\
  106 +--min-version=version sets the minimum PDF version of the output file\n\
  107 +--force-version=version forces this to be the PDF version of the output file\n\
106 \n\ 108 \n\
107 Values for stream data options:\n\ 109 Values for stream data options:\n\
108 \n\ 110 \n\
@@ -119,6 +121,12 @@ Values for object stream mode:\n\ @@ -119,6 +121,12 @@ Values for object stream mode:\n\
119 In qdf mode, by default, content normalization is turned on, and the\n\ 121 In qdf mode, by default, content normalization is turned on, and the\n\
120 stream data mode is set to uncompress.\n\ 122 stream data mode is set to uncompress.\n\
121 \n\ 123 \n\
  124 +Setting the minimum PDF version of the output file may raise the version\n\
  125 +but will never lower it. Forcing the PDF version of the output file may\n\
  126 +set the PDF version to a lower value than actually allowed by the file's\n\
  127 +contents. You should only do this if you have no other possible way to\n\
  128 +open the file or if you know that the file definitely doesn't include\n\
  129 +features not supported later versions.\n\
122 \n\ 130 \n\
123 Testing, Inspection, and Debugging Options\n\ 131 Testing, Inspection, and Debugging Options\n\
124 ------------------------------------------\n\ 132 ------------------------------------------\n\
@@ -518,6 +526,8 @@ int main(int argc, char* argv[]) @@ -518,6 +526,8 @@ int main(int argc, char* argv[])
518 QPDFWriter::object_stream_e object_stream_mode = QPDFWriter::o_preserve; 526 QPDFWriter::object_stream_e object_stream_mode = QPDFWriter::o_preserve;
519 bool ignore_xref_streams = false; 527 bool ignore_xref_streams = false;
520 bool qdf_mode = false; 528 bool qdf_mode = false;
  529 + std::string min_version;
  530 + std::string force_version;
521 531
522 bool static_id = false; 532 bool static_id = false;
523 bool suppress_original_object_id = false; 533 bool suppress_original_object_id = false;
@@ -651,6 +661,24 @@ int main(int argc, char* argv[]) @@ -651,6 +661,24 @@ int main(int argc, char* argv[])
651 { 661 {
652 qdf_mode = true; 662 qdf_mode = true;
653 } 663 }
  664 + else if (strcmp(arg, "min-version") == 0)
  665 + {
  666 + if (parameter == 0)
  667 + {
  668 + usage("--min-version be given as"
  669 + "--min-version=version");
  670 + }
  671 + min_version = parameter;
  672 + }
  673 + else if (strcmp(arg, "force-version") == 0)
  674 + {
  675 + if (parameter == 0)
  676 + {
  677 + usage("--force-version be given as"
  678 + "--force-version=version");
  679 + }
  680 + force_version = parameter;
  681 + }
654 else if (strcmp(arg, "static-id") == 0) 682 else if (strcmp(arg, "static-id") == 0)
655 { 683 {
656 static_id = true; 684 static_id = true;
@@ -977,6 +1005,14 @@ int main(int argc, char* argv[]) @@ -977,6 +1005,14 @@ int main(int argc, char* argv[])
977 { 1005 {
978 w.setObjectStreamMode(object_stream_mode); 1006 w.setObjectStreamMode(object_stream_mode);
979 } 1007 }
  1008 + if (! min_version.empty())
  1009 + {
  1010 + w.setMinimumPDFVersion(min_version);
  1011 + }
  1012 + if (! force_version.empty())
  1013 + {
  1014 + w.forcePDFVersion(force_version);
  1015 + }
980 w.write(); 1016 w.write();
981 } 1017 }
982 if (! pdf.getWarnings().empty()) 1018 if (! pdf.getWarnings().empty())
qpdf/qpdf.testcov
@@ -153,3 +153,8 @@ qpdf-c called qpdf_allow_modify_form 0 @@ -153,3 +153,8 @@ qpdf-c called qpdf_allow_modify_form 0
153 qpdf-c called qpdf_allow_modify_annotation 0 153 qpdf-c called qpdf_allow_modify_annotation 0
154 qpdf-c called qpdf_allow_modify_other 0 154 qpdf-c called qpdf_allow_modify_other 0
155 qpdf-c called qpdf_allow_modify_all 0 155 qpdf-c called qpdf_allow_modify_all 0
  156 +QPDFWriter increasing minimum version 0
  157 +QPDFWriter using forced PDF version 0
  158 +qpdf-c called qpdf_set_minimum_pdf_version 0
  159 +qpdf-c called qpdf_force_pdf_version 0
  160 +qpdf-c called qpdf_init_write multiple times 0
qpdf/qtest/qpdf.test
@@ -81,7 +81,7 @@ flush_tiff_cache(); @@ -81,7 +81,7 @@ flush_tiff_cache();
81 show_ntests(); 81 show_ntests();
82 # ---------- 82 # ----------
83 $td->notify("--- Miscellaneous Tests ---"); 83 $td->notify("--- Miscellaneous Tests ---");
84 -$n_tests += 7; 84 +$n_tests += 14;
85 85
86 foreach (my $i = 1; $i <= 3; ++$i) 86 foreach (my $i = 1; $i <= 3; ++$i)
87 { 87 {
@@ -115,6 +115,44 @@ $td-&gt;runtest(&quot;show new xref stream&quot;, @@ -115,6 +115,44 @@ $td-&gt;runtest(&quot;show new xref stream&quot;,
115 $td->EXIT_STATUS => 0}, 115 $td->EXIT_STATUS => 0},
116 $td->NORMALIZE_NEWLINES); 116 $td->NORMALIZE_NEWLINES);
117 117
  118 +# Min/Force version
  119 +$td->runtest("set min version",
  120 + {$td->COMMAND => "qpdf --min-version=1.6 good1.pdf a.pdf"},
  121 + {$td->STRING => "",
  122 + $td->EXIT_STATUS => 0},
  123 + $td->NORMALIZE_NEWLINES);
  124 +$td->runtest("check version",
  125 + {$td->COMMAND => "qpdf --check a.pdf"},
  126 + {$td->FILE => "min-version.out",
  127 + $td->EXIT_STATUS => 0},
  128 + $td->NORMALIZE_NEWLINES);
  129 +$td->runtest("force version",
  130 + {$td->COMMAND => "qpdf --force-version=1.4 a.pdf b.pdf"},
  131 + {$td->STRING => "",
  132 + $td->EXIT_STATUS => 0},
  133 + $td->NORMALIZE_NEWLINES);
  134 +$td->runtest("check version",
  135 + {$td->COMMAND => "qpdf --check b.pdf"},
  136 + {$td->FILE => "forced-version.out",
  137 + $td->EXIT_STATUS => 0},
  138 + $td->NORMALIZE_NEWLINES);
  139 +unlink "a.pdf", "b.pdf" or die;
  140 +$td->runtest("C API: min/force versions",
  141 + {$td->COMMAND => "qpdf-ctest 14 object-stream.pdf '' a.pdf b.pdf"},
  142 + {$td->STRING => "",
  143 + $td->EXIT_STATUS => 0},
  144 + $td->NORMALIZE_NEWLINES);
  145 +$td->runtest("C check version 1",
  146 + {$td->COMMAND => "qpdf --check a.pdf"},
  147 + {$td->FILE => "min-version.out",
  148 + $td->EXIT_STATUS => 0},
  149 + $td->NORMALIZE_NEWLINES);
  150 +$td->runtest("C check version 2",
  151 + {$td->COMMAND => "qpdf --check b.pdf"},
  152 + {$td->FILE => "forced-version.out",
  153 + $td->EXIT_STATUS => 0},
  154 + $td->NORMALIZE_NEWLINES);
  155 +
118 show_ntests(); 156 show_ntests();
119 # ---------- 157 # ----------
120 $td->notify("--- Error Condition Tests ---"); 158 $td->notify("--- Error Condition Tests ---");
@@ -883,7 +921,7 @@ foreach my $d (@cenc) @@ -883,7 +921,7 @@ foreach my $d (@cenc)
883 my ($n, $infile, $pass, $description, $output) = @$d; 921 my ($n, $infile, $pass, $description, $output) = @$d;
884 my $outfile = $description; 922 my $outfile = $description;
885 $outfile =~ s/ /-/g; 923 $outfile =~ s/ /-/g;
886 - my $outfile = "c-$outfile.pdf"; 924 + $outfile = "c-$outfile.pdf";
887 $td->runtest("C API encryption: $description", 925 $td->runtest("C API encryption: $description",
888 {$td->COMMAND => "qpdf-ctest $n $infile $pass a.pdf"}, 926 {$td->COMMAND => "qpdf-ctest $n $infile $pass a.pdf"},
889 {$td->STRING => $output, $td->EXIT_STATUS => 0}, 927 {$td->STRING => $output, $td->EXIT_STATUS => 0},
qpdf/qtest/qpdf/forced-version.out 0 → 100644
  1 +checking b.pdf
  2 +PDF Version: 1.4
  3 +File is not encrypted
  4 +File is not linearized
  5 +No errors found
qpdf/qtest/qpdf/min-version.out 0 → 100644
  1 +checking a.pdf
  2 +PDF Version: 1.6
  3 +File is not encrypted
  4 +File is not linearized
  5 +No errors found