2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-10-31 19:02:30 +00:00

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> 2021-02-15 Jay Berkenbilt <ejb@ql.org>
* Add a version of QPDFObjectHandle::parse that takes a QPDF* as * Add a version of QPDFObjectHandle::parse that takes a QPDF* as

View File

@ -1077,12 +1077,14 @@ make
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><option>--collate</option></term> <term><option>--collate=<replaceable>n</replaceable></option></term>
<listitem> <listitem>
<para> <para>
When specified, collate rather than concatenate pages from When specified, collate rather than concatenate pages from
files specified with <option>--pages</option>. See <xref files specified with <option>--pages</option>. With a numeric
linkend="ref.page-selection"/> for additional details. argument, collate in groups of <replaceable>n</replaceable>.
The default is 1. See <xref linkend="ref.page-selection"/> for
additional details.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -1646,6 +1648,27 @@ make
<listitem><para>a.pdf page 5</para></listitem> <listitem><para>a.pdf page 5</para></listitem>
</itemizedlist> </itemizedlist>
</para> </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> <para>
Starting in qpdf version 8.3, when you split and merge files, any 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 page labels (page numbers) are preserved in the final file. It is
@ -5144,6 +5167,14 @@ print "\n";
reference to the file spec object. reference to the file spec object.
</para> </para>
</listitem> </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> </itemizedlist>
</listitem> </listitem>
<listitem> <listitem>

View File

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

View File

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