diff options
Diffstat (limited to 'qpdf/qpdf.cc')
-rw-r--r-- | qpdf/qpdf.cc | 346 |
1 files changed, 344 insertions, 2 deletions
diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index e3529550..55157af2 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -60,6 +60,30 @@ struct RotationSpec enum password_mode_e { pm_bytes, pm_hex_bytes, pm_unicode, pm_auto }; +struct UnderOverlay +{ + UnderOverlay(char const* which) : + which(which), + filename(0), + password(0), + to_nr("1-z"), + from_nr("1-z"), + repeat_nr("") + { + } + + std::string which; + char const* filename; + char const* password; + char const* to_nr; + char const* from_nr; + char const* repeat_nr; + PointerHolder<QPDF> pdf; + std::vector<int> to_pagenos; + std::vector<int> from_pagenos; + std::vector<int> repeat_pagenos; +}; + struct Options { Options() : @@ -140,6 +164,9 @@ struct Options oi_min_width(128), // Default values for these oi_min_height(128), // oi flags are in --help oi_min_area(16384), // and in the manual. + underlay("underlay"), + overlay("overlay"), + under_overlay(0), require_outfile(true), infilename(0), outfilename(0) @@ -230,6 +257,9 @@ struct Options size_t oi_min_width; size_t oi_min_height; size_t oi_min_area; + UnderOverlay underlay; + UnderOverlay overlay; + UnderOverlay* under_overlay; std::vector<PageSpec> page_specs; std::map<std::string, RotationSpec> rotations; bool require_outfile; @@ -583,6 +613,8 @@ class ArgParser void argCopyEncryption(char* parameter); void argEncryptionFilePassword(char* parameter); void argPages(); + void argUnderlay(); + void argOverlay(); void argRotate(char* parameter); void argCollate(); void argStreamData(char* parameter); @@ -647,6 +679,12 @@ class ArgParser void arg128ForceV4(); void arg256ForceR5(); void argEndEncrypt(); + void argUOpositional(char* arg); + void argUOto(char* parameter); + void argUOfrom(char* parameter); + void argUOrepeat(char* parameter); + void argUOpassword(char* parameter); + void argEndUnderOverlay(); void usage(std::string const& message); void checkCompletion(); @@ -660,6 +698,7 @@ class ArgParser void addChoicesToCompletions(std::string const&, std::string const&); void handleCompletion(); std::vector<PageSpec> parsePagesOptions(); + void parseUnderOverlayOptions(UnderOverlay*); void parseRotationParameter(std::string const&); std::vector<int> parseNumrange(char const* range, int max, bool throw_error = false); @@ -681,6 +720,7 @@ class ArgParser std::map<std::string, OptionEntry> encrypt40_option_table; std::map<std::string, OptionEntry> encrypt128_option_table; std::map<std::string, OptionEntry> encrypt256_option_table; + std::map<std::string, OptionEntry> under_overlay_option_table; std::vector<PointerHolder<char> > new_argv; std::vector<PointerHolder<char> > bash_argv; PointerHolder<char*> argv_ph; @@ -762,7 +802,8 @@ ArgParser::initOptionTable() t = &this->main_option_table; char const* yn[] = {"y", "n", 0}; (*t)[""] = oe_positional(&ArgParser::argPositional); - (*t)["password"] = oe_requiredParameter(&ArgParser::argPassword, "pass"); + (*t)["password"] = oe_requiredParameter( + &ArgParser::argPassword, "password"); (*t)["empty"] = oe_bare(&ArgParser::argEmpty); (*t)["linearize"] = oe_bare(&ArgParser::argLinearize); (*t)["encrypt"] = oe_bare(&ArgParser::argEncrypt); @@ -859,6 +900,8 @@ ArgParser::initOptionTable() &ArgParser::argOiMinHeight, "minimum-height"); (*t)["oi-min-area"] = oe_requiredParameter( &ArgParser::argOiMinArea, "minimum-area"); + (*t)["overlay"] = oe_bare(&ArgParser::argOverlay); + (*t)["underlay"] = oe_bare(&ArgParser::argUnderlay); t = &this->encrypt40_option_table; (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt); @@ -893,6 +936,18 @@ ArgParser::initOptionTable() t = &this->encrypt256_option_table; (*t)["force-R5"] = oe_bare(&ArgParser::arg256ForceR5); + + t = &this->under_overlay_option_table; + (*t)[""] = oe_positional(&ArgParser::argUOpositional); + (*t)["to"] = oe_requiredParameter( + &ArgParser::argUOto, "page-range"); + (*t)["from"] = oe_requiredParameter( + &ArgParser::argUOfrom, "page-range"); + (*t)["repeat"] = oe_requiredParameter( + &ArgParser::argUOrepeat, "page-range"); + (*t)["password"] = oe_requiredParameter( + &ArgParser::argUOpassword, "password"); + (*t)["--"] = oe_bare(&ArgParser::argEndUnderOverlay); } void @@ -1011,6 +1066,8 @@ ArgParser::argHelp() << " rotate each specified page 90, 180, or 270 degrees;\n" << " rotate all pages if no page range is given\n" << "--split-pages=[n] write each output page to a separate file\n" + << "--overlay options -- overlay pages from another file\n" + << "--underlay options -- underlay pages from another file\n" << "\n" << "Note that you can use the @filename or @- syntax for any argument at any\n" << "point in the command. This provides a good way to specify a password without\n" @@ -1129,6 +1186,7 @@ ArgParser::argHelp() << "\n" << "This is a complex topic. See the manual for a complete discussion.\n" << "\n" + << "\n" << "Page Selection Options\n" << "----------------------\n" << "\n" @@ -1181,6 +1239,35 @@ ArgParser::argHelp() << "See the manual for examples and a discussion of additional subtleties.\n" << "\n" << "\n" + << "Overlay and Underlay Options\n" + << "-------------------------------\n" + << "\n" + << "These options allow pages from another file to be overlaid or underlaid\n" + << "on the primary output. Overlaid pages are drawn on top of the destination\n" + << "page and may obsecure the page. Underlaid pages are drawn below the\n" + << "destination page.\n" + << "\n" + << "{--overlay | --underlay } file\n" + " [ --password=password ]\n" + " [ --to=page-range ]\n" + " [ --from=[page-range] ]\n" + " [ --repeat=page-range ]\n" + " --\n" + << "\n" + << "For overlay and underlay, a file and optional password are specified, along\n" + << "with a series of optional page ranges. The default behavior is that each\n" + << "page of the overlay or underlay file is imposed on the corresponding page\n" + << "of the primary output until it runs out of pages, and any extra pages are\n" + << "ignored. The page range options all take page ranges in the same form as\n" + << "the --pages option. They have the following meanings:\n" + << "\n" + << " --to: the pages in the primary output to which overlay/underlay is\n" + << " applied\n" + << " --from: the pages from the overlay/underlay file that are used\n" + << " --repeat: pages from the overlay/underlay that are repeated after\n" + << " any \"from\" pages have been exhausted\n" + << "\n" + << "\n" << "Advanced Parsing Options\n" << "-------------------------------\n" << "\n" @@ -1529,6 +1616,18 @@ ArgParser::argPages() } void +ArgParser::argUnderlay() +{ + parseUnderOverlayOptions(&o.underlay); +} + +void +ArgParser::argOverlay() +{ + parseUnderOverlayOptions(&o.overlay); +} + +void ArgParser::argRotate(char* parameter) { parseRotationParameter(parameter); @@ -2043,6 +2142,63 @@ ArgParser::argEndEncrypt() } void +ArgParser::argUOpositional(char* arg) +{ + if (o.under_overlay->filename) + { + usage(o.under_overlay->which + " file already specified"); + } + else + { + o.under_overlay->filename = arg; + } +} + +void +ArgParser::argUOto(char* parameter) +{ + parseNumrange(parameter, 0); + o.under_overlay->to_nr = parameter; +} + +void +ArgParser::argUOfrom(char* parameter) +{ + if (strlen(parameter)) + { + parseNumrange(parameter, 0); + } + o.under_overlay->from_nr = parameter; +} + +void +ArgParser::argUOrepeat(char* parameter) +{ + if (strlen(parameter)) + { + parseNumrange(parameter, 0); + } + o.under_overlay->repeat_nr = parameter; +} + +void +ArgParser::argUOpassword(char* parameter) +{ + o.under_overlay->password = parameter; +} + +void +ArgParser::argEndUnderOverlay() +{ + this->option_table = &(this->main_option_table); + if (0 == o.under_overlay->filename) + { + usage(o.under_overlay->which + " file not specified"); + } + o.under_overlay = 0; +} + +void ArgParser::handleArgFileArguments() { // Support reading arguments from files. Create a new argv. Ensure @@ -2398,6 +2554,13 @@ ArgParser::parsePagesOptions() return result; } +void +ArgParser::parseUnderOverlayOptions(UnderOverlay* uo) +{ + o.under_overlay = uo; + this->option_table = &(this->under_overlay_option_table); +} + QPDFPageData::QPDFPageData(std::string const& filename, QPDF* qpdf, char const* range) : @@ -3853,6 +4016,184 @@ static PointerHolder<QPDF> process_input_source( return do_process(&QPDF::processInputSource, is, password, o, false); } +static void validate_under_overlay(QPDF& pdf, UnderOverlay* uo, Options& o) +{ + if (0 == uo->filename) + { + return; + } + QPDFPageDocumentHelper main_pdh(pdf); + int main_npages = static_cast<int>(main_pdh.getAllPages().size()); + uo->pdf = process_file(uo->filename, uo->password, o); + QPDFPageDocumentHelper uo_pdh(*(uo->pdf)); + int uo_npages = static_cast<int>(uo_pdh.getAllPages().size()); + try + { + uo->to_pagenos = QUtil::parse_numrange(uo->to_nr, main_npages); + } + catch (std::runtime_error& e) + { + usageExit("parsing numeric range for " + uo->which + + " \"to\" pages: " + e.what()); + } + try + { + if (0 == strlen(uo->from_nr)) + { + QTC::TC("qpdf", "qpdf from_nr from repeat_nr"); + uo->from_nr = uo->repeat_nr; + } + uo->from_pagenos = QUtil::parse_numrange(uo->from_nr, uo_npages); + if (strlen(uo->repeat_nr)) + { + uo->repeat_pagenos = + QUtil::parse_numrange(uo->repeat_nr, uo_npages); + } + } + catch (std::runtime_error& e) + { + usageExit("parsing numeric range for " + uo->which + " file " + + uo->filename + ": " + e.what()); + } +} + +static void get_uo_pagenos(UnderOverlay& uo, + std::map<int, std::vector<int> >& pagenos) +{ + size_t idx = 0; + size_t from_size = uo.from_pagenos.size(); + size_t repeat_size = uo.repeat_pagenos.size(); + for (std::vector<int>::iterator iter = uo.to_pagenos.begin(); + iter != uo.to_pagenos.end(); ++iter, ++idx) + { + if (idx < from_size) + { + pagenos[*iter].push_back(uo.from_pagenos.at(idx)); + } + else if (repeat_size) + { + pagenos[*iter].push_back( + uo.repeat_pagenos.at((idx - from_size) % repeat_size)); + } + } +} + +static void do_under_overlay_for_page( + QPDF& pdf, + Options& o, + UnderOverlay& uo, + std::map<int, std::vector<int> >& pagenos, + size_t page_idx, + std::map<int, QPDFObjectHandle>& fo, + std::vector<QPDFPageObjectHelper>& pages, + QPDFPageObjectHelper& dest_page, + bool before) +{ + int pageno = 1 + page_idx; + if (! pagenos.count(pageno)) + { + return; + } + + std::string content; + int min_suffix = 1; + QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true); + for (std::vector<int>::iterator iter = pagenos[pageno].begin(); + iter != pagenos[pageno].end(); ++iter) + { + int from_pageno = *iter; + if (o.verbose) + { + std::cout << " " << uo.which << " " << from_pageno << std::endl; + } + if (0 == fo.count(from_pageno)) + { + fo[from_pageno] = + pdf.copyForeignObject( + pages.at(from_pageno - 1).getFormXObjectForPage()); + } + // If the same page is overlaid or underlaid multiple times, + // we'll generate multiple names for it, but that's harmless + // and also a pretty goofy case that's not worth coding + // around. + std::string name = resources.getUniqueResourceName("/Fx", min_suffix); + std::string new_content = dest_page.placeFormXObject( + fo[from_pageno], name, + dest_page.getTrimBox().getArrayAsRectangle()); + if (! new_content.empty()) + { + resources.mergeResources( + QPDFObjectHandle::parse("<< /XObject << >> >>")); + resources.getKey("/XObject").replaceKey(name, fo[from_pageno]); + ++min_suffix; + content += new_content; + } + } + if (! content.empty()) + { + if (before) + { + dest_page.addPageContents( + QPDFObjectHandle::newStream(&pdf, content), true); + } + else + { + dest_page.addPageContents( + QPDFObjectHandle::newStream(&pdf, "q\n"), true); + dest_page.addPageContents( + QPDFObjectHandle::newStream(&pdf, "\nQ\n" + content), false); + } + } +} + +static void handle_under_overlay(QPDF& pdf, Options& o) +{ + validate_under_overlay(pdf, &o.underlay, o); + validate_under_overlay(pdf, &o.overlay, o); + if ((0 == o.underlay.pdf.getPointer()) && + (0 == o.overlay.pdf.getPointer())) + { + return; + } + std::map<int, std::vector<int> > underlay_pagenos; + get_uo_pagenos(o.underlay, underlay_pagenos); + std::map<int, std::vector<int> > overlay_pagenos; + get_uo_pagenos(o.overlay, overlay_pagenos); + std::map<int, QPDFObjectHandle> underlay_fo; + std::map<int, QPDFObjectHandle> overlay_fo; + std::vector<QPDFPageObjectHelper> upages; + if (o.underlay.pdf.getPointer()) + { + upages = QPDFPageDocumentHelper(*(o.underlay.pdf)).getAllPages(); + } + std::vector<QPDFPageObjectHelper> opages; + if (o.overlay.pdf.getPointer()) + { + opages = QPDFPageDocumentHelper(*(o.overlay.pdf)).getAllPages(); + } + + QPDFPageDocumentHelper main_pdh(pdf); + std::vector<QPDFPageObjectHelper> main_pages = main_pdh.getAllPages(); + size_t main_npages = main_pages.size(); + if (o.verbose) + { + std::cout << whoami << ": processing underlay/overlay" << std::endl; + } + for (size_t i = 0; i < main_npages; ++i) + { + if (o.verbose) + { + std::cout << " page " << 1+i << std::endl; + } + do_under_overlay_for_page(pdf, o, o.underlay, underlay_pagenos, i, + underlay_fo, upages, main_pages.at(i), + true); + do_under_overlay_for_page(pdf, o, o.overlay, overlay_pagenos, i, + overlay_fo, opages, main_pages.at(i), + false); + } +} + static void handle_transformations(QPDF& pdf, Options& o) { QPDFPageDocumentHelper dh(pdf); @@ -4636,7 +4977,6 @@ int realmain(int argc, char* argv[]) PointerHolder<QPDF> pdf_ph = process_file(o.infilename, o.password, o); QPDF& pdf = *pdf_ph; - handle_transformations(pdf, o); if (! o.page_specs.empty()) { handle_page_specs(pdf, o); @@ -4645,6 +4985,8 @@ int realmain(int argc, char* argv[]) { handle_rotations(pdf, o); } + handle_under_overlay(pdf, o); + handle_transformations(pdf, o); if (o.outfilename == 0) { |