aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--README4
-rw-r--r--contrib/pdf-rotate.cc373
3 files changed, 1 insertions, 382 deletions
diff --git a/ChangeLog b/ChangeLog
index 93a380dd..87f5a2e2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -141,12 +141,6 @@
* CVE-2017-9210: Fix infinite loop caused by attempting to unparse
an object for inclusion in the text of an exception.
-2016-09-10 Jay Berkenbilt <ejb@ql.org>
-
- * Include pdf-rotate.cc example in contrib. Thanks Iskander
- Sharipov <Iskander.Sharipov@tatar.ru> for contributing this
- program.
-
2015-11-10 Jay Berkenbilt <ejb@ql.org>
* 6.0.0: release
diff --git a/README b/README
index cd5e1945..9e7b885c 100644
--- a/README
+++ b/README
@@ -159,9 +159,7 @@ exercises most of the public interface. There are additional example
programs in the examples directory. Reading all the source files in
the qpdf directory (including the qpdf command-line tool and some test
drivers) along with the code in the examples directory will give you a
-complete picture of every aspect of the public interface. You may also
-check programs in the contrib directory. These are not part of QPDF
-but have been contributed by other developers.
+complete picture of every aspect of the public interface.
Additional Notes on Test Suite
diff --git a/contrib/pdf-rotate.cc b/contrib/pdf-rotate.cc
deleted file mode 100644
index 00556687..00000000
--- a/contrib/pdf-rotate.cc
+++ /dev/null
@@ -1,373 +0,0 @@
-// This program is not part of QPDF, but it is made available with the
-// QPDF software under the same terms as QPDF itself.
-//
-// Author: Iskander Sharipov <Iskander.Sharipov@tatar.ru>
-//
-// This program requires a c++11 compiler but otherwise has no
-// external dependencies beyond QPDF itself.
-
-#include <qpdf/QPDF.hh>
-#include <qpdf/QPDFWriter.hh>
-
-#include <vector>
-#include <regex>
-#include <algorithm>
-
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <cctype>
-#include <cerrno>
-
-#include <sys/stat.h>
-
-using std::vector;
-
-typedef QPDFObjectHandle QpdfObject;
-
-/*
- * Rotates clockwise/counter-clockwise selected pages or page range.
- * It is also capable of setting absolute page rotation.
- * Check `usage` for details.
- *
- * User should check program return value to handle errors.
- * Check `ErrorCode` for enumeration and `error_message` for descriptions.
- */
-
-enum class ErrorCode: int {
- Success, // <- Not an error
- InvalidArgCount,
- InputFileNotExist,
- InvalidDegreeArg,
- InvalidPageSelectorArg,
- InvalidRangeSelector,
- InvalidPagesSelector,
- BadPageNumber,
- BadLowRangeBound,
- BadHighRangeBound,
- UnexpectedException,
- InternalRangeError,
-};
-
-struct Rotation {
- Rotation() = default;
- Rotation(const char* digits);
-
- int degree;
- bool absolute;
-};
-
-struct Arguments {
- const char* in_file_name;
- const char* out_file_name;
- char* pages = nullptr;
- const char* range = nullptr;
- Rotation degree;
-};
-
-void print_error(ErrorCode code);
-Arguments parse_arguments(int argc, char* argv[]);
-void rotate_pages(QPDF& in, QPDF& out, char* selector, Rotation);
-void rotate_page_range(QPDF& in, QPDF& out, const char* range, Rotation);
-void rotate_all_pages(QPDF& in, QPDF& out, Rotation);
-
-int main(int argc, char* argv[]) {
- try {
- Arguments args = parse_arguments(argc, argv);
-
- QPDF in_pdf;
- in_pdf.processFile(args.in_file_name);
- QPDF out_pdf;
- out_pdf.emptyPDF();
-
- if (args.pages) {
- rotate_pages(in_pdf, out_pdf, args.pages, args.degree);
- } else if (args.range) {
- rotate_page_range(in_pdf, out_pdf, args.range, args.degree);
- } else {
- rotate_all_pages(in_pdf, out_pdf, args.degree);
- }
-
- QPDFWriter out_writer{out_pdf, args.out_file_name};
- out_writer.write();
- } catch (ErrorCode e) {
- print_error(e);
- return static_cast<int>(e);
- } catch (...) {
- print_error(ErrorCode::UnexpectedException);
- return static_cast<int>(ErrorCode::UnexpectedException);
- }
-
- return static_cast<int>(ErrorCode::Success);
-}
-
-const int minExpectedArgs = 4;
-const int degreeArgMaxLen = 3;
-
-int try_parse_int(const char* digits, ErrorCode failure_code) {
- char* tail;
- auto result = strtol(digits, &tail, 10);
- auto len = tail - digits;
-
- if (len == 0 || errno == ERANGE) {
- throw failure_code;
- }
-
- return result;
-}
-
-Rotation::Rotation(const char* digits) {
- absolute = isdigit(*digits);
- degree = try_parse_int(digits, ErrorCode::InvalidDegreeArg);
-
- if (degree % 90 != 0) {
- throw ErrorCode::InvalidDegreeArg;
- }
-}
-
-// If error message printing is not required, compile with -DNO_OUTPUT
-#ifdef NO_OUTPUT
-void usage() {}
-void printError(ErrorCode) {}
-#else
-void usage() {
- puts(
- "usage: `qpdf-rotate <in-pdf> <degree> <out-pdf> [<page-selector>]`\n"
- "<in-pdf>: path to input pdf file\n"
- "<degree>: any 90 divisible angle; + or - forces relative rotation\n"
- "<out-pdf>: path for output pdf which will be created\n"
- "note: <in-pdf> must be distinct from <out-pdf>\n"
- "\n"
- "optional page selector arg:\n"
- "one of two possible formats: "
- "1) `--range=<low>-<high>` pages in range (use `z` for last page)\n"
- "2) `--pages=<p1>,...,<pn>` only specified pages (trailing comma is OK)\n"
- "note: (2) option needs sorted comma separated list\n"
- "\n"
- "example: `qpdf-rotate foo.pdf +90 bar.pdf --range=1-10\n"
- "example: `qpdf-rotate foo.pdf 0 bar.pdf --pages=1,2,3,7,10`\n"
- "example: `qpdf-rotate foo.pdf -90 bar.pdf --range=5-z"
- );
-}
-
-const char* error_message(ErrorCode code) {
- switch (code) {
- case ErrorCode::InvalidArgCount:
- return "<in-pdf> or <degree> or <out-pdf> arg is missing";
- case ErrorCode::InputFileNotExist:
- return "<in-pdf> file not found (not exists or can not be accessed)";
- case ErrorCode::InvalidDegreeArg:
- return "<degree> invalid value given";
- case ErrorCode::InvalidPageSelectorArg:
- return "<page-selector> invalid value given";
- case ErrorCode::InvalidRangeSelector:
- return "invalid range selector";
- case ErrorCode::InvalidPagesSelector:
- return "invalid pages selector";
- case ErrorCode::BadLowRangeBound:
- return "bad low range boundary";
- case ErrorCode::BadHighRangeBound:
- return "bad high range boundary";
- case ErrorCode::UnexpectedException:
- return "unexpected exception during execution";
- case ErrorCode::InternalRangeError:
- return "internal range error";
-
- default:
- return "";
- }
-}
-
-void print_error(ErrorCode code) {
- fprintf(stderr, "%s\n", error_message(code));
-}
-#endif
-
-void validate_range_selector(const char* selector) {
- if (!std::regex_match(selector, std::regex("^\\d+\\-(\\d+|z)$"))) {
- throw ErrorCode::InvalidRangeSelector;
- }
-}
-
-void validate_pages_selector(const char* selector) {
- const char* p = selector;
-
- while (*p && isdigit(*p)) {
- while (isdigit(*p)) ++p;
- if (*p && *p == ',') ++p;
- }
-
- if (*p != '\0') {
- throw ErrorCode::InvalidPagesSelector;
- }
-}
-
-inline bool file_exists(const char* name) {
- struct stat file_info;
- return stat(name, &file_info) == 0;
-}
-
-bool has_substr(const char* haystack, const char* needle) {
- return strncmp(haystack, needle, strlen(needle)) == 0;
-}
-
-const char* fetch_range_selector(const char* selector) {
- const char* value = strchr(selector, '=') + 1;
-
- validate_range_selector(value);
-
- return value;
-}
-
-char* fetch_pages_selector(char* selector) {
- char* value = strchr(selector, '=') + 1;
-
- validate_pages_selector(value);
-
- return value;
-}
-
-Arguments parse_arguments(int argc, char* argv[]) {
- if (argc < minExpectedArgs) {
- if (argc == 1) { // Launched without args
- usage();
- }
-
- throw ErrorCode::InvalidArgCount;
- }
-
- enum Argv: int {
- ProgramName,
- InputFile,
- Degree,
- OutputFile,
- PageSelector
- };
-
- Arguments args;
-
- args.in_file_name = argv[Argv::InputFile];
- args.out_file_name = argv[Argv::OutputFile];
-
- if (!file_exists(args.in_file_name)) throw ErrorCode::InputFileNotExist;
-
- args.degree = Rotation{argv[Argv::Degree]};
-
- if (argc > minExpectedArgs) { // Page selector given as an argument
- char* page_selector_arg = argv[Argv::PageSelector];
-
- if (has_substr(page_selector_arg, "--range=")) {
- args.range = fetch_range_selector(page_selector_arg);
- } else if (has_substr(page_selector_arg, "--pages=")) {
- args.pages = fetch_pages_selector(page_selector_arg);
- } else {
- throw ErrorCode::InvalidPageSelectorArg;
- }
- }
-
- return args;
-}
-
-// Simple wrapper around vector<QpdfObject> range
-struct PageRange {
- PageRange(int from, vector<QpdfObject>& pages):
- from_page{from}, to_page{static_cast<int>(pages.size())}, pages{pages} {
- check_invariants();
- }
-
- PageRange(int from, int to, vector<QpdfObject>& pages):
- from_page{from}, to_page{to}, pages{pages} {
- check_invariants();
- }
-
- void check_invariants() {
- if (from_page < 1 || to_page < 1) {
- throw ErrorCode::InternalRangeError;
- }
- }
-
- QpdfObject* begin() const noexcept { return pages.data() + from_page - 1; }
- QpdfObject* end() const noexcept { return pages.data() + to_page; }
-
-private:
- vector<QpdfObject>& pages;
- int to_page;
- int from_page;
-};
-
-QpdfObject calculate_degree(QpdfObject& page, Rotation rotation) {
- int degree = rotation.degree;
-
- if (!rotation.absolute && page.hasKey("/Rotate")) {
- int old_degree = page.getKey("/Rotate").getNumericValue();
- degree += old_degree;
- }
-
- return QpdfObject::newInteger(degree);
-}
-
-void add_rotated_page(QPDF& pdf, QpdfObject& page, Rotation rotation) {
- page.replaceKey("/Rotate", calculate_degree(page, rotation));
- pdf.addPage(page, false);
-}
-
-void add_rotated_pages(QPDF& pdf, PageRange pages, Rotation rotation) {
- for (auto page : pages) {
- add_rotated_page(pdf, page, rotation);
- }
-}
-
-void add_pages(QPDF& pdf, PageRange pages) {
- for (auto page : pages) {
- pdf.addPage(page, false);
- }
-}
-
-void rotate_pages(QPDF& in, QPDF& out, char* selector, Rotation rotation) {
- static const int unparsed = -1;
-
- auto pages = in.getAllPages();
- auto digits = strtok(selector, ",");
- int n = unparsed;
-
- for (int page_n = 0; page_n < pages.size(); ++page_n) {
- if (digits && n == unparsed) {
- n = try_parse_int(digits, ErrorCode::BadPageNumber) - 1;
- }
-
- if (n == page_n) {
- digits = strtok(nullptr, ",");
- n = unparsed;
- add_rotated_page(out, pages[page_n], rotation);
- } else {
- out.addPage(pages[page_n], false);
- }
- }
-}
-
-void rotate_page_range(QPDF& in, QPDF& out, const char* range, Rotation rotation) {
- auto pages = in.getAllPages();
-
- int from_page = try_parse_int(range, ErrorCode::BadLowRangeBound);
- int to_page;
-
- if (range[(strlen(range)-1)] == 'z') {
- to_page = pages.size();
- } else {
- to_page = try_parse_int(strchr(range, '-') + 1, ErrorCode::BadHighRangeBound);
- }
-
- if (from_page > 1) {
- add_pages(out, PageRange{1, from_page - 1, pages});
- }
- add_rotated_pages(out, PageRange{from_page, to_page, pages}, rotation);
- if (to_page < pages.size()) {
- add_pages(out, PageRange(to_page + 1, pages));
- }
-}
-
-void rotate_all_pages(QPDF& in, QPDF& out, Rotation rotation) {
- auto pages = in.getAllPages();
- add_rotated_pages(out, PageRange{1, pages}, rotation);
-}
-