Add numeric argument to --collate

This takes pages from the file in groups of n with default = 1. This
partially fixes the enhancement in issue #505 but doesn't implement
the entire suggestion.
This commit is contained in:
Jay Berkenbilt 2021-02-17 20:04:55 -05:00
parent a773f4c71d
commit dfce581754
5 changed files with 1051 additions and 21 deletions

View File

@ -1,3 +1,9 @@
2021-02-17 Jay Berkenbilt <ejb@ql.org>
* Allow optional numeric argument to --collate. If --collate=n is
given, pull n pages from the first file, n pages from the second
file, etc., until we run out of pages.
2021-02-15 Jay Berkenbilt <ejb@ql.org>
* Add a version of QPDFObjectHandle::parse that takes a QPDF* as

View File

@ -1077,12 +1077,14 @@ make
</listitem>
</varlistentry>
<varlistentry>
<term><option>--collate</option></term>
<term><option>--collate=<replaceable>n</replaceable></option></term>
<listitem>
<para>
When specified, collate rather than concatenate pages from
files specified with <option>--pages</option>. See <xref
linkend="ref.page-selection"/> for additional details.
files specified with <option>--pages</option>. With a numeric
argument, collate in groups of <replaceable>n</replaceable>.
The default is 1. See <xref linkend="ref.page-selection"/> for
additional details.
</para>
</listitem>
</varlistentry>
@ -1646,6 +1648,27 @@ make
<listitem><para>a.pdf page 5</para></listitem>
</itemizedlist>
</para>
<para>
Starting in qpdf version 10.2, you may specify a numeric argument
to <option>--collate</option>. With
<option>--collate=<replaceable>n</replaceable></option>, pull
groups of <replaceable>n</replaceable> pages from each file,
again, stopping when there are no more pages. For example, if you
ran <command>qpdf --collate=2 --empty --pages a.pdf 1-5 b.pdf 6-4
c.pdf r1 -- out.pdf</command>, you would get the following pages
in this order:
<itemizedlist>
<listitem><para>a.pdf page 1</para></listitem>
<listitem><para>a.pdf page 2</para></listitem>
<listitem><para>b.pdf page 6</para></listitem>
<listitem><para>b.pdf page 5</para></listitem>
<listitem><para>c.pdf last page</para></listitem>
<listitem><para>a.pdf page 3</para></listitem>
<listitem><para>a.pdf page 4</para></listitem>
<listitem><para>b.pdf page 4</para></listitem>
<listitem><para>a.pdf page 5</para></listitem>
</itemizedlist>
</para>
<para>
Starting in qpdf version 8.3, when you split and merge files, any
page labels (page numbers) are preserved in the final file. It is
@ -5144,6 +5167,14 @@ print "\n";
reference to the file spec object.
</para>
</listitem>
<listitem>
<para>
Add numeric option to <option>--collate</option>. If
<option>--collate=<replaceable>n</replaceable></option> is
given, take pages in groups of <replaceable>n</replaceable>
from the given files.
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>

View File

@ -201,7 +201,7 @@ struct Options
show_filtered_stream_data(false),
show_pages(false),
show_page_images(false),
collate(false),
collate(0),
flatten_rotation(false),
list_attachments(false),
json(false),
@ -307,7 +307,7 @@ struct Options
bool show_filtered_stream_data;
bool show_pages;
bool show_page_images;
bool collate;
size_t collate;
bool flatten_rotation;
bool list_attachments;
std::string attachment_to_show;
@ -804,7 +804,7 @@ class ArgParser
void argUnderlay();
void argOverlay();
void argRotate(char* parameter);
void argCollate();
void argCollate(char* parameter);
void argFlattenRotation();
void argListAttachments();
void argShowAttachment(char* parameter);
@ -1048,7 +1048,7 @@ ArgParser::initOptionTable()
&ArgParser::argRotate, "[+|-]angle:page-range");
char const* stream_data_choices[] =
{"compress", "preserve", "uncompress", 0};
(*t)["collate"] = oe_bare(&ArgParser::argCollate);
(*t)["collate"] = oe_optionalParameter(&ArgParser::argCollate);
(*t)["flatten-rotation"] = oe_bare(&ArgParser::argFlattenRotation);
(*t)["list-attachments"] = oe_bare(&ArgParser::argListAttachments);
(*t)["show-attachment"] = oe_requiredParameter(
@ -1369,8 +1369,9 @@ ArgParser::argHelp()
<< " encoding errors\n"
<< "--password-mode=mode control qpdf's encoding of passwords\n"
<< "--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"
<< "--collate=n causes files specified in --pages to be collated\n"
<< " in groups of n pages (default 1) rather than\n"
<< " 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"
@ -2068,9 +2069,11 @@ ArgParser::argEncryptionFilePassword(char* parameter)
}
void
ArgParser::argCollate()
ArgParser::argCollate(char* parameter)
{
o.collate = true;
auto n = ((parameter == 0) ? 1 :
QUtil::string_to_uint(parameter));
o.collate = QIntC::to_size(n);
}
void
@ -5788,16 +5791,19 @@ static void handle_page_specs(QPDF& pdf, Options& o, bool& warnings)
for (size_t i = 0; i < nspecs; ++i)
{
QPDFPageData& page_data = parsed_specs.at(i);
if (cur_page < page_data.selected_pages.size())
for (size_t j = 0; j < o.collate; ++j)
{
got_pages = true;
new_parsed_specs.push_back(
QPDFPageData(
page_data,
page_data.selected_pages.at(cur_page)));
if (cur_page + j < page_data.selected_pages.size())
{
got_pages = true;
new_parsed_specs.push_back(
QPDFPageData(
page_data,
page_data.selected_pages.at(cur_page + j)));
}
}
}
++cur_page;
cur_page += o.collate;
}
parsed_specs = new_parsed_specs;
}

View File

@ -2573,17 +2573,26 @@ show_ntests();
# ----------
$td->notify("--- Collating ---");
my @collate = (
["three-files", "collate-odd",
["", "three-files", "collate-odd",
"collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"],
[1, "three-files", "collate-odd",
"collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"],
[2, "three-files-2", "collate-odd",
"collate-odd.pdf 1-5 minimal.pdf collate-even.pdf 7-1"],
);
$n_tests += 2 * scalar(@collate);
foreach my $d (@collate)
{
my ($description, $first, $args) = @$d;
my ($n, $description, $first, $args) = @$d;
my $collate = '--collate';
if ($n)
{
$collate .= "=$n";
}
$td->runtest("collate pages: $description",
{$td->COMMAND =>
"qpdf --qdf --static-id --collate $first.pdf" .
"qpdf --qdf --static-id $collate $first.pdf" .
" --pages $args -- a.pdf"},
{$td->STRING => "", $td->EXIT_STATUS => 0});
$td->runtest("check output",

View File

@ -0,0 +1,978 @@
%PDF-1.3
%¿÷¢þ
%QDF-1.0
%% Original object ID: 1 0
1 0 obj
<<
/Outlines 2 0 R
/PageLabels <<
/Nums [
0
<<
/P ()
/St 1
>>
1
<<
/S /r
/St 1
>>
2
<<
/St 3
>>
3
<<
/S /D
/St 3
>>
4
<<
/P ()
/St 1
>>
5
<<
/S /r
/St 3
>>
6
<<
/S /r
/St 5
>>
8
<<
/P ()
/St 1
>>
10
<<
/S /r
/St 4
>>
11
<<
/S /r
/St 2
>>
12
<<
/P ()
/St 2
>>
]
>>
/PageMode /UseOutlines
/Pages 3 0 R
/Type /Catalog
>>
endobj
%% Original object ID: 2 0
2 0 obj
<<
/Count 6
/First 4 0 R
/Last 5 0 R
/Type /Outlines
>>
endobj
%% Original object ID: 3 0
3 0 obj
<<
/Count 13
/Kids [
6 0 R
7 0 R
8 0 R
9 0 R
10 0 R
11 0 R
12 0 R
13 0 R
14 0 R
15 0 R
16 0 R
17 0 R
18 0 R
]
/Type /Pages
>>
endobj
%% Original object ID: 4 0
4 0 obj
<<
/Count 4
/Dest [
null
/XYZ
null
null
null
]
/First 19 0 R
/Last 20 0 R
/Next 5 0 R
/Parent 2 0 R
/Title (Isís 1 -> 5: /XYZ null null null)
/Type /Outline
>>
endobj
%% Original object ID: 5 0
5 0 obj
<<
/Dest [
null
/XYZ
66
756
3
]
/Parent 2 0 R
/Prev 4 0 R
/Title (Trepak 2 -> 15: /XYZ 66 756 3)
/Type /Outline
>>
endobj
%% Page 1
%% Original object ID: 6 0
6 0 obj
<<
/Contents 21 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 23 0 R
>>
/ProcSet 24 0 R
>>
/Type /Page
>>
endobj
%% Page 2
%% Original object ID: 7 0
7 0 obj
<<
/Contents 25 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 23 0 R
>>
/ProcSet 24 0 R
>>
/Type /Page
>>
endobj
%% Page 3
%% Original object ID: 47 0
8 0 obj
<<
/Contents 27 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 29 0 R
>>
/ProcSet 30 0 R
>>
/Type /Page
>>
endobj
%% Page 4
%% Original object ID: 51 0
9 0 obj
<<
/Contents 31 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 33 0 R
>>
/ProcSet 34 0 R
>>
/Type /Page
>>
endobj
%% Page 5
%% Original object ID: 55 0
10 0 obj
<<
/Contents 35 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 33 0 R
>>
/ProcSet 34 0 R
>>
/Type /Page
>>
endobj
%% Page 6
%% Original object ID: 8 0
11 0 obj
<<
/Contents 37 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 23 0 R
>>
/ProcSet 24 0 R
>>
/Type /Page
>>
endobj
%% Page 7
%% Original object ID: 9 0
12 0 obj
<<
/Contents 39 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 23 0 R
>>
/ProcSet 24 0 R
>>
/Type /Page
>>
endobj
%% Page 8
%% Original object ID: 57 0
13 0 obj
<<
/Contents 41 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 33 0 R
>>
/ProcSet 34 0 R
>>
/Type /Page
>>
endobj
%% Page 9
%% Original object ID: 59 0
14 0 obj
<<
/Contents 43 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 33 0 R
>>
/ProcSet 34 0 R
>>
/Type /Page
>>
endobj
%% Page 10
%% Original object ID: 10 0
15 0 obj
<<
/Contents 45 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 23 0 R
>>
/ProcSet 24 0 R
>>
/Type /Page
>>
endobj
%% Page 11
%% Original object ID: 61 0
16 0 obj
<<
/Contents 47 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 33 0 R
>>
/ProcSet 34 0 R
>>
/Type /Page
>>
endobj
%% Page 12
%% Original object ID: 63 0
17 0 obj
<<
/Contents 49 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 33 0 R
>>
/ProcSet 34 0 R
>>
/Type /Page
>>
endobj
%% Page 13
%% Original object ID: 65 0
18 0 obj
<<
/Contents 51 0 R
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 33 0 R
>>
/ProcSet 34 0 R
>>
/Type /Page
>>
endobj
%% Original object ID: 21 0
19 0 obj
<<
/Count -3
/Dest [
null
/Fit
]
/First 53 0 R
/Last 54 0 R
/Next 20 0 R
/Parent 4 0 R
/Title (Amanda 1.1 -> 11: /Fit)
/Type /Outline
>>
endobj
%% Original object ID: 22 0
20 0 obj
<<
/Count 2
/Dest [
null
/FitH
792
]
/First 55 0 R
/Last 56 0 R
/Parent 4 0 R
/Prev 19 0 R
/Title <feff00530061006e00640079002000f703a303b103bd03b403b900f700200031002e00320020002d003e002000310033003a0020002f00460069007400480020003700390032>
/Type /Outline
>>
endobj
%% Contents for page 1
%% Original object ID: 23 0
21 0 obj
<<
/Length 22 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 0) Tj
ET
endstream
endobj
22 0 obj
46
endobj
%% Original object ID: 24 0
23 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
%% Original object ID: 25 0
24 0 obj
[
/PDF
/Text
]
endobj
%% Contents for page 2
%% Original object ID: 26 0
25 0 obj
<<
/Length 26 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 2) Tj
ET
endstream
endobj
26 0 obj
46
endobj
%% Contents for page 3
%% Original object ID: 48 0
27 0 obj
<<
/Length 28 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
28 0 obj
44
endobj
%% Original object ID: 49 0
29 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
%% Original object ID: 50 0
30 0 obj
[
/PDF
/Text
]
endobj
%% Contents for page 4
%% Original object ID: 52 0
31 0 obj
<<
/Length 32 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 13) Tj
ET
endstream
endobj
32 0 obj
47
endobj
%% Original object ID: 53 0
33 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
%% Original object ID: 54 0
34 0 obj
[
/PDF
/Text
]
endobj
%% Contents for page 5
%% Original object ID: 56 0
35 0 obj
<<
/Length 36 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 11) Tj
ET
endstream
endobj
36 0 obj
47
endobj
%% Contents for page 6
%% Original object ID: 27 0
37 0 obj
<<
/Length 38 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 4) Tj
ET
endstream
endobj
38 0 obj
46
endobj
%% Contents for page 7
%% Original object ID: 28 0
39 0 obj
<<
/Length 40 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 6) Tj
ET
endstream
endobj
40 0 obj
46
endobj
%% Contents for page 8
%% Original object ID: 58 0
41 0 obj
<<
/Length 42 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 9) Tj
ET
endstream
endobj
42 0 obj
46
endobj
%% Contents for page 9
%% Original object ID: 60 0
43 0 obj
<<
/Length 44 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 7) Tj
ET
endstream
endobj
44 0 obj
46
endobj
%% Contents for page 10
%% Original object ID: 29 0
45 0 obj
<<
/Length 46 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 8) Tj
ET
endstream
endobj
46 0 obj
46
endobj
%% Contents for page 11
%% Original object ID: 62 0
47 0 obj
<<
/Length 48 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 5) Tj
ET
endstream
endobj
48 0 obj
46
endobj
%% Contents for page 12
%% Original object ID: 64 0
49 0 obj
<<
/Length 50 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 3) Tj
ET
endstream
endobj
50 0 obj
46
endobj
%% Contents for page 13
%% Original object ID: 66 0
51 0 obj
<<
/Length 52 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato 1) Tj
ET
endstream
endobj
52 0 obj
46
endobj
%% Original object ID: 40 0
53 0 obj
<<
/Count -2
/Dest [
57 0 R
/FitV
100
]
/First 58 0 R
/Last 59 0 R
/Next 54 0 R
/Parent 19 0 R
/Title (Isosicle 1.1.1 -> 12: /FitV 100)
/Type /Outline
>>
endobj
%% Original object ID: 41 0
54 0 obj
<<
/Count 1
/Dest [
57 0 R
/XYZ
null
null
null
]
/First 60 0 R
/Last 60 0 R
/Parent 19 0 R
/Prev 53 0 R
/Title (Isosicle 1.1.2 -> 12: /XYZ null null null)
/Type /Outline
>>
endobj
%% Original object ID: 42 0
55 0 obj
<<
/Dest [
null
/FitR
66
714
180
770
]
/Next 56 0 R
/Parent 20 0 R
/Title (Trepsichord 1.2.1 -> 1: /FitR 66 714 180 770)
/Type /Outline
>>
endobj
%% Original object ID: 43 0
56 0 obj
<<
/Dest [
6 0 R
/XYZ
null
null
null
]
/Parent 20 0 R
/Prev 55 0 R
/Title (Trepsicle 1.2.2 -> 0: /XYZ null null null)
/Type /Outline
>>
endobj
%% Original object ID: 12 0
57 0 obj
null
endobj
%% Original object ID: 44 0
58 0 obj
<<
/Dest [
61 0 R
/XYZ
null
null
null
]
/Next 59 0 R
/Parent 53 0 R
/Title (Isosicle 1.1.1.1 -> 18: /XYZ null null null)
/Type /Outline
>>
endobj
%% Original object ID: 45 0
59 0 obj
<<
/Dest [
null
/XYZ
null
null
null
]
/Parent 53 0 R
/Prev 58 0 R
/Title (Isosicle 1.1.1.2 -> 19: /XYZ null null null)
/Type /Outline
>>
endobj
%% Original object ID: 46 0
60 0 obj
<<
/Dest [
62 0 R
/XYZ
null
null
null
]
/Parent 54 0 R
/Title (Isosicle 1.1.2.1 -> 22: /XYZ null null null)
/Type /Outline
>>
endobj
%% Original object ID: 15 0
61 0 obj
null
endobj
%% Original object ID: 17 0
62 0 obj
null
endobj
xref
0 63
0000000000 65535 f
0000000052 00000 n
0000000799 00000 n
0000000906 00000 n
0000001135 00000 n
0000001376 00000 n
0000001576 00000 n
0000001808 00000 n
0000002041 00000 n
0000002274 00000 n
0000002507 00000 n
0000002740 00000 n
0000002973 00000 n
0000003207 00000 n
0000003441 00000 n
0000003676 00000 n
0000003911 00000 n
0000004146 00000 n
0000004381 00000 n
0000004605 00000 n
0000004813 00000 n
0000005170 00000 n
0000005273 00000 n
0000005321 00000 n
0000005468 00000 n
0000005555 00000 n
0000005658 00000 n
0000005729 00000 n
0000005830 00000 n
0000005878 00000 n
0000006025 00000 n
0000006112 00000 n
0000006216 00000 n
0000006264 00000 n
0000006411 00000 n
0000006498 00000 n
0000006602 00000 n
0000006673 00000 n
0000006776 00000 n
0000006847 00000 n
0000006950 00000 n
0000007021 00000 n
0000007124 00000 n
0000007195 00000 n
0000007298 00000 n
0000007370 00000 n
0000007473 00000 n
0000007545 00000 n
0000007648 00000 n
0000007720 00000 n
0000007823 00000 n
0000007895 00000 n
0000007998 00000 n
0000008046 00000 n
0000008275 00000 n
0000008531 00000 n
0000008751 00000 n
0000008964 00000 n
0000009014 00000 n
0000009230 00000 n
0000009444 00000 n
0000009645 00000 n
0000009695 00000 n
trailer <<
/Root 1 0 R
/Size 63
/ID [<d3fab8d0603e683dc94e42ac31141868><31415926535897932384626433832795>]
>>
startxref
9717
%%EOF