aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--TODO4
-rw-r--r--include/qpdf/QPDFPageObjectHelper.hh10
-rw-r--r--libqpdf/QPDFPageObjectHelper.cc132
-rw-r--r--manual/qpdf-manual.xml13
-rw-r--r--qpdf/qpdf.cc18
-rw-r--r--qpdf/qtest/qpdf.test11
-rw-r--r--qpdf/qtest/qpdf/boxes-flattened.pdf907
-rw-r--r--qpdf/qtest/qpdf/boxes.pdf685
9 files changed, 1786 insertions, 5 deletions
diff --git a/ChangeLog b/ChangeLog
index 6fc963f3..9703de5f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2020-12-30 Jay Berkenbilt <ejb@ql.org>
+
+ * Add QPDFPageObjectHelper::flattenRotation and --flatten-rotation
+ option to the qpdf CLI. The flattenRotation method removes any
+ /Rotate key from a page dictionary and implements the same
+ rotation by modifying the page's contents such that the various
+ page boxes are altered and the page renders identically. This can
+ be used to work around buggy PDF applications that don't properly
+ handle page rotation. The --flatten-rotation option to the qpdf
+ CLI calls flattenRotation for every page.
+
2020-12-26 Jay Berkenbilt <ejb@ql.org>
* Add QPDFObjectHandle::setFilterOnWrite, which can be used to
diff --git a/TODO b/TODO
index 444535f3..a07c93d3 100644
--- a/TODO
+++ b/TODO
@@ -39,10 +39,6 @@ Candidates for upcoming release
* big page even with --remove-unreferenced-resources=yes, even with --empty
* optimize image failure because of colorspace
-* Take flattenRotation code from pdf-split and do something with it,
- maybe adding it to the library. Once there, call it from pdf-split
- and bump up the required version of qpdf.
-
* Externalize inline images doesn't walk into form XObjects. In
general:
diff --git a/include/qpdf/QPDFPageObjectHelper.hh b/include/qpdf/QPDFPageObjectHelper.hh
index a197ece7..42d67088 100644
--- a/include/qpdf/QPDFPageObjectHelper.hh
+++ b/include/qpdf/QPDFPageObjectHelper.hh
@@ -242,6 +242,16 @@ class QPDFPageObjectHelper: public QPDFObjectHelper
bool allow_shrink = true,
bool allow_expand = false);
+ // If a page is rotated using /Rotate in the page's dictionary,
+ // instead rotate the page by the same amount by altering the
+ // contents and removing the /Rotate key. This method adjusts the
+ // various page bounding boxes (/MediaBox, etc.) so that the page
+ // will have the same semantics. This can be useful to work around
+ // problems with PDF applications that can't properly handle
+ // rotated pages.
+ QPDF_DLL
+ void flattenRotation();
+
private:
static void
removeUnreferencedResourcesHelper(
diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc
index b7da816e..58747814 100644
--- a/libqpdf/QPDFPageObjectHelper.cc
+++ b/libqpdf/QPDFPageObjectHelper.cc
@@ -835,3 +835,135 @@ QPDFPageObjectHelper::placeFormXObject(
name + " Do\n" +
"Q\n");
}
+
+void
+QPDFPageObjectHelper::flattenRotation()
+{
+ QPDF* qpdf = this->oh.getOwningQPDF();
+ if (! qpdf)
+ {
+ throw std::runtime_error(
+ "QPDFPageObjectHelper::flattenRotation"
+ " called with a direct object");
+ }
+
+ auto rotate_oh = this->oh.getKey("/Rotate");
+ int rotate = 0;
+ if (rotate_oh.isInteger())
+ {
+ rotate = rotate_oh.getIntValueAsInt();
+ }
+ if (! ((rotate == 90) || (rotate == 180) || (rotate == 270)))
+ {
+ return;
+ }
+ auto mediabox = this->oh.getKey("/MediaBox");
+ if (! mediabox.isRectangle())
+ {
+ return;
+ }
+ auto media_rect = mediabox.getArrayAsRectangle();
+
+ std::vector<std::string> boxes = {
+ "/MediaBox", "/CropBox", "/BleedBox", "/TrimBox", "/ArtBox",
+ };
+ for (auto const& boxkey: boxes)
+ {
+ auto box = this->oh.getKey(boxkey);
+ if (! box.isRectangle())
+ {
+ continue;
+ }
+ auto rect = box.getArrayAsRectangle();
+ decltype(rect) new_rect;
+
+ // How far are the edges of our rectangle from the edges
+ // of the media box?
+ auto left_x = rect.llx - media_rect.llx;
+ auto right_x = media_rect.urx - rect.urx;
+ auto bottom_y = rect.lly - media_rect.lly;
+ auto top_y = media_rect.ury - rect.ury;
+
+ // Rotating the page 180 degrees does not change
+ // /MediaBox. Rotating 90 or 270 degrees reverses llx and
+ // lly and also reverse urx and ury. For all the other
+ // boxes, we want the corners to be the correct distance
+ // away from the corners of the mediabox.
+ switch (rotate)
+ {
+ case 90:
+ new_rect.llx = media_rect.lly + bottom_y;
+ new_rect.urx = media_rect.ury - top_y;
+ new_rect.lly = media_rect.llx + right_x;
+ new_rect.ury = media_rect.urx - left_x;
+ break;
+
+ case 180:
+ new_rect.llx = media_rect.llx + right_x;
+ new_rect.urx = media_rect.urx - left_x;
+ new_rect.lly = media_rect.lly + top_y;
+ new_rect.ury = media_rect.ury - bottom_y;
+ break;
+
+ case 270:
+ new_rect.llx = media_rect.lly + top_y;
+ new_rect.urx = media_rect.ury - bottom_y;
+ new_rect.lly = media_rect.llx + left_x;
+ new_rect.ury = media_rect.urx - right_x;
+ break;
+
+ default:
+ // ignore
+ break;
+ }
+
+ this->oh.replaceKey(
+ boxkey, QPDFObjectHandle::newFromRectangle(new_rect));
+ }
+
+ // When we rotate the page, pivot about the point 0, 0 and then
+ // translate so the page is visible with the origin point being
+ // the same offset from the lower left corner of the media box.
+ // These calculations have been verified emperically with various
+ // PDF readers.
+ QPDFObjectHandle::Matrix cm;
+ cm.e = 0.0;
+ cm.f = 0.0;
+ switch (rotate)
+ {
+ case 90:
+ cm.b = -1;
+ cm.c = 1;
+ cm.f = media_rect.urx + media_rect.llx;
+ break;
+
+ case 180:
+ cm.a = -1;
+ cm.d = -1;
+ cm.e = media_rect.urx + media_rect.llx;
+ cm.f = media_rect.ury + media_rect.lly;
+ break;
+
+ case 270:
+ cm.b = 1;
+ cm.c = -1;
+ cm.e = media_rect.ury + media_rect.lly;
+ break;
+
+ default:
+ break;
+ }
+ std::string cm_str =
+ std::string("q\n") +
+ QUtil::double_to_string(cm.a, 2) + " " +
+ QUtil::double_to_string(cm.b, 2) + " " +
+ QUtil::double_to_string(cm.c, 2) + " " +
+ QUtil::double_to_string(cm.d, 2) + " " +
+ QUtil::double_to_string(cm.e, 2) + " " +
+ QUtil::double_to_string(cm.f, 2) + " cm\n";
+ this->oh.addPageContents(
+ QPDFObjectHandle::newStream(qpdf, cm_str), true);
+ this->oh.addPageContents(
+ QPDFObjectHandle::newStream(qpdf, "\nQ\n"), false);
+ this->oh.removeKey("/Rotate");
+}
diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml
index 10a165d3..8caab104 100644
--- a/manual/qpdf-manual.xml
+++ b/manual/qpdf-manual.xml
@@ -1063,6 +1063,19 @@ make
</listitem>
</varlistentry>
<varlistentry>
+ <term><option>--flatten-rotation</option></term>
+ <listitem>
+ <para>
+ For each page that is rotated using the
+ <literal>/Rotate</literal> key in the page's dictionary,
+ remove the <literal>/Rotate</literal> key and implement the
+ identical rotation semantics by modifying the page's contents.
+ This option can be useful to prepare files for buggy PDF
+ applications that don't properly handle rotated pages.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
<term><option>--split-pages=[n]</option></term>
<listitem>
<para>
diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc
index 83670596..19d7b0c4 100644
--- a/qpdf/qpdf.cc
+++ b/qpdf/qpdf.cc
@@ -174,6 +174,7 @@ struct Options
show_pages(false),
show_page_images(false),
collate(false),
+ flatten_rotation(false),
json(false),
check(false),
optimize_images(false),
@@ -276,6 +277,7 @@ struct Options
bool show_pages;
bool show_page_images;
bool collate;
+ bool flatten_rotation;
bool json;
std::set<std::string> json_keys;
std::set<std::string> json_objects;
@@ -747,6 +749,7 @@ class ArgParser
void argOverlay();
void argRotate(char* parameter);
void argCollate();
+ void argFlattenRotation();
void argStreamData(char* parameter);
void argCompressStreams(char* parameter);
void argRecompressFlate();
@@ -968,6 +971,7 @@ ArgParser::initOptionTable()
char const* stream_data_choices[] =
{"compress", "preserve", "uncompress", 0};
(*t)["collate"] = oe_bare(&ArgParser::argCollate);
+ (*t)["flatten-rotation"] = oe_bare(&ArgParser::argFlattenRotation);
(*t)["stream-data"] = oe_requiredChoices(
&ArgParser::argStreamData, stream_data_choices);
(*t)["compress-streams"] = oe_requiredChoices(
@@ -1250,6 +1254,7 @@ ArgParser::argHelp()
<< "--pages options -- select specific pages from one or more files\n"
<< "--collate causes files specified in --pages to be collated\n"
<< " rather than concatenated\n"
+ << "--flatten-rotation move page rotation from /Rotate key to content\n"
<< "--rotate=[+|-]angle[:page-range]\n"
<< " rotate each specified page 90, 180, or 270 degrees;\n"
<< " rotate all pages if no page range is given\n"
@@ -1880,6 +1885,12 @@ ArgParser::argRotate(char* parameter)
}
void
+ArgParser::argFlattenRotation()
+{
+ o.flatten_rotation = true;
+}
+
+void
ArgParser::argStreamData(char* parameter)
{
o.stream_data_set = true;
@@ -4818,6 +4829,13 @@ static void handle_transformations(QPDF& pdf, Options& o)
(*iter).coalesceContentStreams();
}
}
+ if (o.flatten_rotation)
+ {
+ for (auto& page: dh.getAllPages())
+ {
+ page.flattenRotation();
+ }
+ }
if (o.remove_page_labels)
{
pdf.getRoot().removeKey("/PageLabels");
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index b166db84..1fdc5f09 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -671,7 +671,7 @@ foreach my $d (@json_files)
show_ntests();
# ----------
$td->notify("--- Page API Tests ---");
-$n_tests += 9;
+$n_tests += 11;
$td->runtest("basic page API",
{$td->COMMAND => "test_driver 15 page_api_1.pdf"},
@@ -706,6 +706,15 @@ $td->runtest("remove page we don't have",
{$td->COMMAND => "test_driver 22 page_api_1.pdf"},
{$td->FILE => "page_api_1.out2", $td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
+$td->runtest("flatten rotation",
+ {$td->COMMAND => "qpdf --static-id --qdf".
+ " --no-original-object-ids" .
+ " --flatten-rotation boxes.pdf a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "boxes-flattened.pdf"});
show_ntests();
# ----------
$td->notify("--- Files for specific bugs ---");
diff --git a/qpdf/qtest/qpdf/boxes-flattened.pdf b/qpdf/qtest/qpdf/boxes-flattened.pdf
new file mode 100644
index 00000000..97effcef
--- /dev/null
+++ b/qpdf/qtest/qpdf/boxes-flattened.pdf
@@ -0,0 +1,907 @@
+%PDF-1.3
+%¿÷¢þ
+%QDF-1.0
+
+1 0 obj
+<<
+ /Pages 2 0 R
+ /Type /Catalog
+>>
+endobj
+
+2 0 obj
+<<
+ /Count 8
+ /Kids [
+ 3 0 R
+ 4 0 R
+ 5 0 R
+ 6 0 R
+ 7 0 R
+ 8 0 R
+ 9 0 R
+ 10 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+3 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 11 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 2
+4 0 obj
+<<
+ /BleedBox [
+ 40.000000
+ 60.000000
+ 712.000000
+ 592.000000
+ ]
+ /Contents [
+ 17 0 R
+ 19 0 R
+ 21 0 R
+ ]
+ /CropBox [
+ 20.000000
+ 30.000000
+ 752.000000
+ 602.000000
+ ]
+ /MediaBox [
+ 0.000000
+ 0.000000
+ 792.000000
+ 612.000000
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 60.000000
+ 90.000000
+ 672.000000
+ 582.000000
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 3
+5 0 obj
+<<
+ /BleedBox [
+ 60.000000
+ 80.000000
+ 592.000000
+ 752.000000
+ ]
+ /Contents [
+ 23 0 R
+ 25 0 R
+ 27 0 R
+ ]
+ /CropBox [
+ 30.000000
+ 40.000000
+ 602.000000
+ 772.000000
+ ]
+ /MediaBox [
+ 0.000000
+ 0.000000
+ 612.000000
+ 792.000000
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 90.000000
+ 120.000000
+ 582.000000
+ 732.000000
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 4
+6 0 obj
+<<
+ /BleedBox [
+ 80.000000
+ 20.000000
+ 752.000000
+ 552.000000
+ ]
+ /Contents [
+ 29 0 R
+ 31 0 R
+ 33 0 R
+ ]
+ /CropBox [
+ 40.000000
+ 10.000000
+ 772.000000
+ 582.000000
+ ]
+ /MediaBox [
+ 0.000000
+ 0.000000
+ 792.000000
+ 612.000000
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 120.000000
+ 30.000000
+ 732.000000
+ 522.000000
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 5
+7 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 35 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ -10
+ -20
+ 642
+ 832
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 6
+8 0 obj
+<<
+ /BleedBox [
+ 40.000000
+ 80.000000
+ 712.000000
+ 612.000000
+ ]
+ /Contents [
+ 37 0 R
+ 39 0 R
+ 41 0 R
+ ]
+ /CropBox [
+ 20.000000
+ 50.000000
+ 752.000000
+ 622.000000
+ ]
+ /MediaBox [
+ -20.000000
+ -10.000000
+ 832.000000
+ 642.000000
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 60.000000
+ 110.000000
+ 672.000000
+ 602.000000
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 7
+9 0 obj
+<<
+ /BleedBox [
+ 80.000000
+ 100.000000
+ 612.000000
+ 772.000000
+ ]
+ /Contents [
+ 43 0 R
+ 45 0 R
+ 47 0 R
+ ]
+ /CropBox [
+ 50.000000
+ 60.000000
+ 622.000000
+ 792.000000
+ ]
+ /MediaBox [
+ -10.000000
+ -20.000000
+ 642.000000
+ 832.000000
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 110.000000
+ 140.000000
+ 602.000000
+ 752.000000
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 8
+10 0 obj
+<<
+ /BleedBox [
+ 100.000000
+ 20.000000
+ 772.000000
+ 552.000000
+ ]
+ /Contents [
+ 49 0 R
+ 51 0 R
+ 53 0 R
+ ]
+ /CropBox [
+ 60.000000
+ 10.000000
+ 792.000000
+ 582.000000
+ ]
+ /MediaBox [
+ -20.000000
+ -10.000000
+ 832.000000
+ 642.000000
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 140.000000
+ 30.000000
+ 752.000000
+ 522.000000
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+11 0 obj
+<<
+ /Length 12 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 0) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+12 0 obj
+115
+endobj
+
+13 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+14 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+15 0 obj
+<<
+ /BBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 55 0 R
+ >>
+ /Subtype /Form
+ /Type /XObject
+ /Length 16 0 R
+>>
+stream
+BT
+ /F1 12 Tf
+ 144 620 Td
+ 1 0 0 rg
+ (red rectangle at media [0 0 612 792]) Tj
+ 0 -15 Td
+ 0 1 0 rg
+ (green at crop [10 20 582 752]) Tj
+ 0 -15 Td
+ 0 0 1 rg
+ (blue at bleed [20 40 552 712]) Tj
+ 0 -15 Td
+ 1 .5 0 rg
+ (orange at trim [30 60 522 672]) Tj
+ 0 -15 Td
+ 0 0 0 rg
+ (page is cropped at crop) Tj
+ 0 -15 Td
+ 0 0 0 rg
+ (red media rectangle is not visible) Tj
+ET
+5 w
+1 0 0 RG
+0 0 612 792 re s
+0 1 0 RG
+10 20 572 732 re s
+0 0 1 RG
+20 40 532 672 re s
+0 0 0 rg
+1 .5 0 RG
+30 60 492 612 re s
+endstream
+endobj
+
+16 0 obj
+506
+endobj
+
+%% Contents for page 2
+17 0 obj
+<<
+ /Length 18 0 R
+>>
+stream
+q
+0.00 -1.00 1.00 0.00 0.00 612.00 cm
+endstream
+endobj
+
+18 0 obj
+38
+endobj
+
+%% Contents for page 2
+19 0 obj
+<<
+ /Length 20 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 90) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+20 0 obj
+116
+endobj
+
+%% Contents for page 2
+21 0 obj
+<<
+ /Length 22 0 R
+>>
+stream
+
+Q
+endstream
+endobj
+
+22 0 obj
+3
+endobj
+
+%% Contents for page 3
+23 0 obj
+<<
+ /Length 24 0 R
+>>
+stream
+q
+-1.00 0.00 0.00 -1.00 612.00 792.00 cm
+endstream
+endobj
+
+24 0 obj
+41
+endobj
+
+%% Contents for page 3
+25 0 obj
+<<
+ /Length 26 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 180) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+26 0 obj
+117
+endobj
+
+%% Contents for page 3
+27 0 obj
+<<
+ /Length 28 0 R
+>>
+stream
+
+Q
+endstream
+endobj
+
+28 0 obj
+3
+endobj
+
+%% Contents for page 4
+29 0 obj
+<<
+ /Length 30 0 R
+>>
+stream
+q
+0.00 1.00 -1.00 0.00 792.00 0.00 cm
+endstream
+endobj
+
+30 0 obj
+38
+endobj
+
+%% Contents for page 4
+31 0 obj
+<<
+ /Length 32 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 270) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+32 0 obj
+117
+endobj
+
+%% Contents for page 4
+33 0 obj
+<<
+ /Length 34 0 R
+>>
+stream
+
+Q
+endstream
+endobj
+
+34 0 obj
+3
+endobj
+
+%% Contents for page 5
+35 0 obj
+<<
+ /Length 36 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 0, extended MediaBox) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+36 0 obj
+134
+endobj
+
+%% Contents for page 6
+37 0 obj
+<<
+ /Length 38 0 R
+>>
+stream
+q
+0.00 -1.00 1.00 0.00 0.00 632.00 cm
+endstream
+endobj
+
+38 0 obj
+38
+endobj
+
+%% Contents for page 6
+39 0 obj
+<<
+ /Length 40 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 90, extended MediaBox) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+40 0 obj
+135
+endobj
+
+%% Contents for page 6
+41 0 obj
+<<
+ /Length 42 0 R
+>>
+stream
+
+Q
+endstream
+endobj
+
+42 0 obj
+3
+endobj
+
+%% Contents for page 7
+43 0 obj
+<<
+ /Length 44 0 R
+>>
+stream
+q
+-1.00 0.00 0.00 -1.00 632.00 812.00 cm
+endstream
+endobj
+
+44 0 obj
+41
+endobj
+
+%% Contents for page 7
+45 0 obj
+<<
+ /Length 46 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 180, extended MediaBox) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+46 0 obj
+136
+endobj
+
+%% Contents for page 7
+47 0 obj
+<<
+ /Length 48 0 R
+>>
+stream
+
+Q
+endstream
+endobj
+
+48 0 obj
+3
+endobj
+
+%% Contents for page 8
+49 0 obj
+<<
+ /Length 50 0 R
+>>
+stream
+q
+0.00 1.00 -1.00 0.00 812.00 0.00 cm
+endstream
+endobj
+
+50 0 obj
+38
+endobj
+
+%% Contents for page 8
+51 0 obj
+<<
+ /Length 52 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 270, extended MediaBox) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+52 0 obj
+136
+endobj
+
+%% Contents for page 8
+53 0 obj
+<<
+ /Length 54 0 R
+>>
+stream
+
+Q
+endstream
+endobj
+
+54 0 obj
+3
+endobj
+
+55 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 56
+0000000000 65535 f
+0000000025 00000 n
+0000000079 00000 n
+0000000232 00000 n
+0000000620 00000 n
+0000001152 00000 n
+0000001685 00000 n
+0000002218 00000 n
+0000002610 00000 n
+0000003147 00000 n
+0000003686 00000 n
+0000004238 00000 n
+0000004410 00000 n
+0000004431 00000 n
+0000004550 00000 n
+0000004586 00000 n
+0000005303 00000 n
+0000005347 00000 n
+0000005442 00000 n
+0000005485 00000 n
+0000005658 00000 n
+0000005702 00000 n
+0000005762 00000 n
+0000005804 00000 n
+0000005902 00000 n
+0000005945 00000 n
+0000006119 00000 n
+0000006163 00000 n
+0000006223 00000 n
+0000006265 00000 n
+0000006360 00000 n
+0000006403 00000 n
+0000006577 00000 n
+0000006621 00000 n
+0000006681 00000 n
+0000006723 00000 n
+0000006914 00000 n
+0000006958 00000 n
+0000007053 00000 n
+0000007096 00000 n
+0000007288 00000 n
+0000007332 00000 n
+0000007392 00000 n
+0000007434 00000 n
+0000007532 00000 n
+0000007575 00000 n
+0000007768 00000 n
+0000007812 00000 n
+0000007872 00000 n
+0000007914 00000 n
+0000008009 00000 n
+0000008052 00000 n
+0000008245 00000 n
+0000008289 00000 n
+0000008349 00000 n
+0000008368 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 56
+ /ID [<42ed290ee4e4c51171853f92a1a7642d><31415926535897932384626433832795>]
+>>
+startxref
+8404
+%%EOF
diff --git a/qpdf/qtest/qpdf/boxes.pdf b/qpdf/qtest/qpdf/boxes.pdf
new file mode 100644
index 00000000..fba1b969
--- /dev/null
+++ b/qpdf/qtest/qpdf/boxes.pdf
@@ -0,0 +1,685 @@
+%PDF-1.3
+%¿÷¢þ
+%QDF-1.0
+
+1 0 obj
+<<
+ /Pages 2 0 R
+ /Type /Catalog
+>>
+endobj
+
+2 0 obj
+<<
+ /Count 8
+ /Kids [
+ 3 0 R
+ 4 0 R
+ 5 0 R
+ 6 0 R
+ 7 0 R
+ 8 0 R
+ 9 0 R
+ 10 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+3 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 11 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 2
+4 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 17 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /Rotate 90
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 3
+5 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 19 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /Rotate 180
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 4
+6 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 21 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /Rotate 270
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 5
+7 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 23 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ -10
+ -20
+ 642
+ 832
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 6
+8 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 25 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ -10
+ -20
+ 642
+ 832
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /Rotate 90
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 7
+9 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 27 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ -10
+ -20
+ 642
+ 832
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /Rotate 180
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 8
+10 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 29 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ -10
+ -20
+ 642
+ 832
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 14 0 R
+ /XObject <<
+ /Fx1 15 0 R
+ >>
+ >>
+ /Rotate 270
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+11 0 obj
+<<
+ /Length 12 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 0) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+12 0 obj
+115
+endobj
+
+13 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+14 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+15 0 obj
+<<
+ /BBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Resources <<
+ /Font <<
+ /F1 13 0 R
+ >>
+ /ProcSet 31 0 R
+ >>
+ /Subtype /Form
+ /Type /XObject
+ /Length 16 0 R
+>>
+stream
+BT
+ /F1 12 Tf
+ 144 620 Td
+ 1 0 0 rg
+ (red rectangle at media [0 0 612 792]) Tj
+ 0 -15 Td
+ 0 1 0 rg
+ (green at crop [10 20 582 752]) Tj
+ 0 -15 Td
+ 0 0 1 rg
+ (blue at bleed [20 40 552 712]) Tj
+ 0 -15 Td
+ 1 .5 0 rg
+ (orange at trim [30 60 522 672]) Tj
+ 0 -15 Td
+ 0 0 0 rg
+ (page is cropped at crop) Tj
+ 0 -15 Td
+ 0 0 0 rg
+ (red media rectangle is not visible) Tj
+ET
+5 w
+1 0 0 RG
+0 0 612 792 re s
+0 1 0 RG
+10 20 572 732 re s
+0 0 1 RG
+20 40 532 672 re s
+0 0 0 rg
+1 .5 0 RG
+30 60 492 612 re s
+endstream
+endobj
+
+16 0 obj
+506
+endobj
+
+%% Contents for page 2
+17 0 obj
+<<
+ /Length 18 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 90) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+18 0 obj
+116
+endobj
+
+%% Contents for page 3
+19 0 obj
+<<
+ /Length 20 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 180) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+20 0 obj
+117
+endobj
+
+%% Contents for page 4
+21 0 obj
+<<
+ /Length 22 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 270) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+22 0 obj
+117
+endobj
+
+%% Contents for page 5
+23 0 obj
+<<
+ /Length 24 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 0, extended MediaBox) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+24 0 obj
+134
+endobj
+
+%% Contents for page 6
+25 0 obj
+<<
+ /Length 26 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 90, extended MediaBox) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+26 0 obj
+135
+endobj
+
+%% Contents for page 7
+27 0 obj
+<<
+ /Length 28 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 180, extended MediaBox) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+28 0 obj
+136
+endobj
+
+%% Contents for page 8
+29 0 obj
+<<
+ /Length 30 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 500 Td
+ (Rotate: 270, extended MediaBox) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+30 0 obj
+136
+endobj
+
+31 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 32
+0000000000 65535 f
+0000000025 00000 n
+0000000079 00000 n
+0000000232 00000 n
+0000000620 00000 n
+0000001021 00000 n
+0000001423 00000 n
+0000001825 00000 n
+0000002217 00000 n
+0000002622 00000 n
+0000003028 00000 n
+0000003448 00000 n
+0000003620 00000 n
+0000003641 00000 n
+0000003760 00000 n
+0000003796 00000 n
+0000004513 00000 n
+0000004557 00000 n
+0000004730 00000 n
+0000004774 00000 n
+0000004948 00000 n
+0000004992 00000 n
+0000005166 00000 n
+0000005210 00000 n
+0000005401 00000 n
+0000005445 00000 n
+0000005637 00000 n
+0000005681 00000 n
+0000005874 00000 n
+0000005918 00000 n
+0000006111 00000 n
+0000006132 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 32
+ /ID [<42ed290ee4e4c51171853f92a1a7642d><a4dda9b40735c4bc1bf243bdd415193e>]
+>>
+startxref
+6168
+%%EOF