aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog7
-rw-r--r--manual/qpdf-manual.xml27
-rw-r--r--qpdf/qpdf.cc40
-rw-r--r--qpdf/qtest/qpdf.test16
4 files changed, 75 insertions, 15 deletions
diff --git a/ChangeLog b/ChangeLog
index 0bb24590..dc6fd816 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2018-03-04 Jay Berkenbilt <ejb@ql.org>
+
+ * On the command line when specifying page ranges, support
+ preceding a page number by "r" to indicate that it should be
+ counted from the end. For example, the range r3-r1 would indicate
+ the last three pages of a document.
+
2018-03-03 Jay Berkenbilt <ejb@ql.org>
* Ignore zlib data check errors while uncompressing streams. This
diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml
index 7f9404c6..6003cabe 100644
--- a/manual/qpdf-manual.xml
+++ b/manual/qpdf-manual.xml
@@ -818,13 +818,15 @@ make
</para>
<para>
The page range is a set of numbers separated by commas, ranges of
- numbers separated dashes, or combinations of those. The character
- &ldquo;z&rdquo; represents the last page. Pages can appear in any
- order. Ranges can appear with a high number followed by a low
- number, which causes the pages to appear in reverse. Repeating a
- number will cause an error, but you can use the workaround
- discussed above should you really want to include the same page
- twice.
+ numbers separated dashes, or combinations of those. The character
+ &ldquo;z&rdquo; represents the last page. A number preceded by an
+ &ldquo;r&rdquo; indicates to count from the end, so
+ &ldquo;r3-r1&rdquo; would be the last three pages of the document.
+ Pages can appear in any order. Ranges can appear with a high
+ number followed by a low number, which causes the pages to appear
+ in reverse. Repeating a number will cause an error, but you can
+ use the workaround discussed above should you really want to
+ include the same page twice.
</para>
<para>
Example page ranges:
@@ -840,6 +842,17 @@ make
<literal>z-1</literal>: all pages in the document in reverse
</para>
</listitem>
+ <listitem>
+ <para>
+ <literal>r3-r1</literal>: the last three pages of the document
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>r1-r3</literal>: the last three pages of the document
+ in reverse order
+ </para>
+ </listitem>
</itemizedlist>
</para>
<para>
diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc
index 17130e8d..caeda8e4 100644
--- a/qpdf/qpdf.cc
+++ b/qpdf/qpdf.cc
@@ -358,11 +358,12 @@ input.\n\
\n\
The page range is a set of numbers separated by commas, ranges of\n\
numbers separated dashes, or combinations of those. The character\n\
-\"z\" represents the last page. Pages can appear in any order. Ranges\n\
-can appear with a high number followed by a low number, which causes the\n\
-pages to appear in reverse. Repeating a number will cause an error, but\n\
-the manual discusses a workaround should you really want to include the\n\
-same page twice.\n\
+\"z\" represents the last page. A number preceded by an \"r\" indicates\n\
+to count from the end, so \"r3-r1\" would be the last three pages of the\n\
+document. Pages can appear in any order. Ranges can appear with a\n\
+high number followed by a low number, which causes the pages to appear in\n\
+reverse. Repeating a number will cause an error, but the manual discusses\n\
+a workaround should you really want to include the same page twice.\n\
\n\
If the page range is omitted, the range of 1-z is assumed. qpdf decides\n\
that the page range is omitted if the range argument is either -- or a\n\
@@ -577,6 +578,22 @@ static void show_encryption(QPDF& pdf, Options& o)
}
}
+static int maybe_from_end(int num, bool from_end, int max)
+{
+ if (from_end)
+ {
+ if (num > max)
+ {
+ num = 0;
+ }
+ else
+ {
+ num = max + 1 - num;
+ }
+ }
+ return num;
+}
+
static std::vector<int> parse_numrange(char const* range, int max,
bool throw_error = false)
{
@@ -593,6 +610,7 @@ static std::vector<int> parse_numrange(char const* range, int max,
st_after_number } state = st_top;
bool last_separator_was_dash = false;
int cur_number = 0;
+ bool from_end = false;
while (*p)
{
char ch = *p;
@@ -616,14 +634,25 @@ static std::vector<int> parse_numrange(char const* range, int max,
state = st_after_number;
cur_number = max;
}
+ else if (ch == 'r')
+ {
+ if (! (state == st_top))
+ {
+ throw std::runtime_error("r not expected");
+ }
+ state = st_in_number;
+ from_end = true;
+ }
else if ((ch == ',') || (ch == '-'))
{
if (! ((state == st_in_number) || (state == st_after_number)))
{
throw std::runtime_error("unexpected separator");
}
+ cur_number = maybe_from_end(cur_number, from_end, max);
work.push_back(cur_number);
cur_number = 0;
+ from_end = false;
if (ch == ',')
{
state = st_top;
@@ -649,6 +678,7 @@ static std::vector<int> parse_numrange(char const* range, int max,
}
if ((state == st_in_number) || (state == st_after_number))
{
+ cur_number = maybe_from_end(cur_number, from_end, max);
work.push_back(cur_number);
}
else
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index c6e39fb8..69d5a5de 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -1092,9 +1092,19 @@ my @nrange_tests = (
["1-10,1234,5",
"qpdf: error in numeric range 1-10,1234,5: number 1234 out of range",
2],
- ["1,3,5-10,z-13,13,9,z,2",
- "numeric range 1,3,5-10,z-13,13,9,z,2" .
- " -> 1 3 5 6 7 8 9 10 15 14 13 13 9 15 2",
+ ["1,r,3",
+ "qpdf: error in numeric range 1,r,3: number 16 out of range",
+ 2],
+ ["1,r16,3",
+ "qpdf: error in numeric range 1,r16,3: number 0 out of range",
+ 2],
+ ["1,3,5-10,z-13,13,9,z,2,r2-r4",
+ "numeric range 1,3,5-10,z-13,13,9,z,2,r2-r4" .
+ " -> 1 3 5 6 7 8 9 10 15 14 13 13 9 15 2 14 13 12",
+ 0],
+ ["r1-r15", # r\d+ at end
+ "numeric range r1-r15" .
+ " -> 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1",
0],
);
$n_tests += scalar(@nrange_tests);